WeakMap and WeakSet

JavaScript has weak versions of Map and Set. They hold their keys or values weakly, which means they do not prevent garbage collection. When an object is no longer referenced anywhere else, it can be cleaned up automatically, and its entry in the weak collection goes with it.

WeakMap

A WeakMap is a Map where keys are held weakly.

Key Characteristics

  • Keys must be objects (or non-registered symbols in modern engines)
  • Registered symbols (Symbol.for(...)) are not valid weak keys
  • Not iterable (no forEach, keys(), values(), or size)
  • Entries can be garbage collected when the key object is no longer referenced
const weakMap = new WeakMap();

let user = { name: "Alice" };
weakMap.set(user, { visits: 10 });

console.log(weakMap.get(user)); // { visits: 10 }

user = null; // The entry can now be garbage collected

Use Case: Private Data

WeakMap is commonly used to store private data associated with objects:

const privateData = new WeakMap();

class Person {
  constructor(name, ssn) {
    this.name = name;
    privateData.set(this, { ssn });
  }

  getSSN() {
    return privateData.get(this).ssn;
  }
}

const alice = new Person("Alice", "123-45-6789");
console.log(alice.name); // "Alice"
console.log(alice.getSSN()); // "123-45-6789"
console.log(alice.ssn); // undefined - not directly accessible

When the Person instance is garbage collected, its private data is automatically cleaned up.

Use Case: Caching

WeakMap works well for caching computed results:

const cache = new WeakMap();

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

  console.log("Computing...");
  const result = { computed: obj.value * 2 };
  cache.set(obj, result);
  return result;
}

const data = { value: 21 };
expensiveOperation(data); // "Computing..." → { computed: 42 }
expensiveOperation(data); // "Cache hit" → { computed: 42 }

WeakSet

A WeakSet is a Set where values are held weakly.

Key Characteristics

  • Values must be objects (or non-registered symbols in modern engines)
  • Registered symbols (Symbol.for(...)) are not valid weak values
  • Not iterable (no forEach or size)
  • Values can be garbage collected when no longer referenced elsewhere
const weakSet = new WeakSet();

let obj = { id: 1 };
weakSet.add(obj);

console.log(weakSet.has(obj)); // true

obj = null; // The entry can now be garbage collected

Use Case: Tracking Objects

WeakSet is useful for tracking which objects have been processed:

const processed = new WeakSet();

function processOnce(obj) {
  if (processed.has(obj)) {
    console.log("Already processed");
    return;
  }

  console.log("Processing:", obj.id);
  processed.add(obj);
}

const item = { id: 42 };
processOnce(item); // "Processing: 42"
processOnce(item); // "Already processed"

Use Case: Marking Objects

const marked = new WeakSet();

function markAsVisited(node) {
  marked.add(node);
}

function hasBeenVisited(node) {
  return marked.has(node);
}

Summary

Collection Keys/Values Iterable Use Case
Map Any type -> Any Yes Key-value pairs with non-string keys
Set Unique values Yes Unique collections, membership testing
WeakMap Objects/non-registered symbols -> Any No Private data, caching without leaks
WeakSet Objects/non-registered symbols No Tracking objects without preventing GC