Skip to content
Codeloom
JavaScript

WeakMap and WeakSet Use Cases in JavaScript

Understand JavaScript WeakMap and WeakSet, their garbage collection behavior, and practical use cases like caching, private data, and DOM metadata.

·6 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • How WeakMap and WeakSet differ from Map and Set
  • Why weak references matter for memory management
  • Practical patterns: private data, caching, DOM tracking

Prerequisites

  • Basic JavaScript (objects, Map, Set)
  • Understanding of garbage collection concepts

WeakMap and WeakSet are specialized collections that hold weak references to their keys (or values, for WeakSet). This means the garbage collector can reclaim objects stored in them when no other references exist. This subtle difference from Map and Set unlocks important patterns for memory-safe programming.

WeakMap vs Map at a glance

// Regular Map -- keys are strongly referenced
const map = new Map();
let obj = { name: "Alice" };
map.set(obj, "some data");
obj = null; // object is NOT garbage collected -- Map still holds it

// WeakMap -- keys are weakly referenced
const weakMap = new WeakMap();
let user = { name: "Bob" };
weakMap.set(user, "some data");
user = null; // object CAN be garbage collected

Key differences:

FeatureMapWeakMap
Key typesAny valueObjects only
IterableYesNo
.sizeYesNo
GC of keysNoYes

WeakMap keys must be objects (or non-registered symbols). You cannot use strings or numbers as keys. You also cannot iterate over a WeakMap or check its size — this is by design, because entries may disappear at any time.

Use case 1: Private instance data

Before private class fields (#), WeakMap was the standard way to store truly private data.

const privateData = new WeakMap();

class User {
  constructor(name, password) {
    this.name = name;
    privateData.set(this, { password });
  }

  checkPassword(attempt) {
    return privateData.get(this).password === attempt;
  }
}

const user = new User("Alice", "secret123");
console.log(user.name);              // "Alice"
console.log(user.password);          // undefined -- not accessible
console.log(user.checkPassword("secret123")); // true

When the User instance is garbage collected, the private data stored in the WeakMap is automatically cleaned up too. No memory leaks.

Use case 2: Caching expensive computations

WeakMap makes an excellent cache that cleans itself up when objects are no longer used.

const cache = new WeakMap();

function expensiveComputation(obj) {
  if (cache.has(obj)) {
    console.log("Cache hit");
    return cache.get(obj);
  }

  console.log("Computing...");
  const result = {
    hash: JSON.stringify(obj).split("").reduce((a, c) => a + c.charCodeAt(0), 0),
    timestamp: Date.now(),
  };

  cache.set(obj, result);
  return result;
}

let data = { x: 1, y: 2 };
expensiveComputation(data); // "Computing..."
expensiveComputation(data); // "Cache hit"

data = null; // cached result is eligible for GC

Compare this to a regular Map cache, which would hold references to every object you ever computed, causing a memory leak in long-running applications.

Use case 3: Associating metadata with DOM elements

You often need to attach data to DOM elements without modifying the elements themselves.

const elementData = new WeakMap();

function trackElement(element, metadata) {
  elementData.set(element, {
    ...elementData.get(element),
    ...metadata,
  });
}

function getElementData(element) {
  return elementData.get(element) || {};
}

// Attach data to a DOM element
const button = document.querySelector("#submit");
trackElement(button, { clickCount: 0, lastClicked: null });

button.addEventListener("click", () => {
  const data = getElementData(button);
  data.clickCount++;
  data.lastClicked = Date.now();
  trackElement(button, data);
});

// When the button is removed from the DOM and all references are gone,
// the associated metadata is automatically garbage collected.

Use case 4: Marking objects as “processed”

This is where WeakSet shines. Use it to track which objects have been visited or processed.

const visited = new WeakSet();

function deepClone(obj) {
  // Prevent infinite loops with circular references
  if (visited.has(obj)) {
    return "[Circular]";
  }
  visited.add(obj);

  if (typeof obj !== "object" || obj === null) {
    return obj;
  }

  const clone = Array.isArray(obj) ? [] : {};
  for (const key of Object.keys(obj)) {
    clone[key] = deepClone(obj[key]);
  }
  return clone;
}

const a = { name: "node" };
a.self = a; // circular reference
console.log(deepClone(a)); // { name: "node", self: "[Circular]" }

Use case 5: Tracking active class instances

WeakSet is useful for keeping track of instances without preventing their garbage collection.

const activeConnections = new WeakSet();

class Connection {
  constructor(url) {
    this.url = url;
    activeConnections.add(this);
  }

  isActive() {
    return activeConnections.has(this);
  }

  close() {
    activeConnections.delete(this);
  }
}

let conn = new Connection("wss://example.com");
console.log(conn.isActive()); // true
conn.close();
console.log(conn.isActive()); // false

Use case 6: Memoization with object arguments

When your function takes an object argument, a WeakMap-based memoizer avoids memory leaks.

function memoizeByObject(fn) {
  const cache = new WeakMap();
  return function (obj) {
    if (cache.has(obj)) {
      return cache.get(obj);
    }
    const result = fn(obj);
    cache.set(obj, result);
    return result;
  };
}

const getFullName = memoizeByObject((user) => {
  console.log("Computing full name...");
  return `${user.first} ${user.last}`;
});

const user = { first: "Jane", last: "Doe" };
getFullName(user); // "Computing full name..." -> "Jane Doe"
getFullName(user); // "Jane Doe" (cached, no log)

WeakRef and FinalizationRegistry

ES2021 introduced WeakRef and FinalizationRegistry for even more granular control.

let bigData = { numbers: new Array(1000000).fill(0) };
const ref = new WeakRef(bigData);

console.log(ref.deref()); // { numbers: [...] }

bigData = null;
// After GC runs:
// ref.deref() returns undefined

// FinalizationRegistry lets you run cleanup code
const registry = new FinalizationRegistry((heldValue) => {
  console.log(`${heldValue} was garbage collected`);
});

let obj = { id: 42 };
registry.register(obj, "Object #42");
obj = null; // Eventually logs: "Object #42 was garbage collected"

Use these sparingly. They are non-deterministic — you cannot predict when GC will run.

Common mistakes

Mistake 1: Trying to iterate a WeakMap

const wm = new WeakMap();
// wm.keys()    -- TypeError: wm.keys is not a function
// wm.forEach() -- TypeError: wm.forEach is not a function
// for...of     -- TypeError: wm is not iterable

If you need iteration, use a regular Map.

Mistake 2: Using primitive keys

const wm = new WeakMap();
// wm.set("key", "value"); -- TypeError: Invalid value used as weak map key

Keys must be objects or non-registered symbols.

Summary

  • WeakMap holds weak references to object keys, allowing automatic garbage collection.
  • WeakSet holds weak references to object values, useful for tagging or tracking objects.
  • Neither is iterable or has a .size property.
  • Key use cases: private data, self-cleaning caches, DOM metadata, circular reference detection, and instance tracking.
  • Use WeakRef and FinalizationRegistry for advanced GC-aware patterns, but sparingly.

Choosing between Map/Set and WeakMap/WeakSet comes down to one question: should the collection prevent garbage collection of its entries? If the answer is no, reach for the weak variant.