ESC

Type to search the knowledge base.

WeakMap and WeakSet

Hold object keys without preventing GC — private data, DOM metadata, and why WeakMap isn’t iterable.

advanced3 min read
  • javascript
  • weakmap-and

Map and Set keep strong references to their keys/values — an entry pins objects in memory. WeakMap / WeakSet hold weak references to object keys: if nothing else points at the key, the entry can disappear with GC. Perfect for metadata that should die with the object.

const meta = new WeakMap();

const el = document.createElement('div');
meta.set(el, { clicks: 0 });
meta.get(el).clicks++;

// when el is unreachable, meta entry can be collected

Constraints

Map WeakMap
Key types any objects (or symbols historically limited; objects only for classic WM)
Iterable yes no
size yes no
GC strong weak keys
// TypeError — primitive keys
// weak.set('x', 1);

Same for WeakSet: only objects, no iteration, no size.

const seen = new WeakSet();
function process(obj) {
  if (seen.has(obj)) return;
  seen.add(obj);
  // ...
}

Privacy before #fields

const _password = new WeakMap();

class User {
  constructor(pw) {
    _password.set(this, pw);
  }
  check(pw) {
    return _password.get(this) === pw;
  }
}

Only code with _password can read it. Module scope keeps the WeakMap closed over. Today prefer #password for class fields; WeakMap still useful for external association (libraries attaching data to user objects without mutating them).

DOM and listeners

const controllers = new WeakMap();

function enhance(form) {
  const ac = new AbortController();
  form.addEventListener('input', onInput, { signal: ac.signal });
  controllers.set(form, ac);
}

function teardown(form) {
  controllers.get(form)?.abort();
  // if form is discarded without teardown, weak entry can GC —
  // but the listener might still be held by the DOM node itself
}

WeakMap doesn’t remove listeners for you; it avoids extra maps keeping nodes alive after removal from the document if nothing else references them.

Why no iteration

If you could enumerate weak keys, you’d observe GC nondeterministically — a security/spec nightmare. Design APIs that already have the object key when looking up.

WeakRef and FinalizationRegistry

Related but different: WeakRef points at an object without keeping it; FinalizationRegistry schedules a callback after collection (best-effort, not for logic correctness). Prefer WeakMap for associated data; don’t build app logic on finalizers.

Interview answer (out loud)

“WeakMap keys are objects held weakly so entries don’t prevent GC. There’s no size or iteration because enumeration would expose GC. I use WeakMaps for private metadata and library side tables. WeakSet is a weak object set. Neither accepts primitive keys.”

Memoization with object identity

const cache = new WeakMap();
function derived(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const value = compute(obj);
  cache.set(obj, value);
  return value;
}

When the object is GC’d, the memo entry can go too — unlike a Map that leaks. Only works when the key is an object and identity is the right equality.

WeakSet for brand checks

const instances = new WeakSet();
class Token {
  constructor() { instances.add(this); }
  static isToken(x) { return instances.has(x); }
}

Similar spirit to private brand checks with # fields.

Further reading

Related guides