WeakMap and WeakSet
Hold object keys without preventing GC — private data, DOM metadata, and why WeakMap isn’t iterable.
- 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
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.