WeakMap and WeakSet
WeakMap and WeakSet hold their object keys/values weakly — meaning the garbage collector is free to reclaim an object even while it's still a key in a WeakMap, as long as nothing else references it. A regular Map holding the same object would keep it alive forever (a memory leak) until explicitly deleted.
🎯 What you'll learn
- The Core Difference from Map/Set: Weak References
- Key Restrictions (What Makes Them "Weak")
- Practical Use Case: Private Data Without
#privateFields - Practical Use Case: Tagging Objects Without Modifying Them
- DOM Node Metadata (Classic Browser Use Case)
1The Core Difference from Map/Set: Weak References
WeakMap and WeakSet hold their object keys/values weakly — meaning the garbage collector is free to reclaim an object even while it's still a key in a WeakMap, as long as nothing else references it. A regular Map holding the same object would keep it alive forever (a memory leak) until explicitly deleted.
let user = { name: "Ana" };
const metadata = new WeakMap();
metadata.set(user, { lastLogin: "2024-01-01" });
user = null; // no other references to the original object remain
// The engine is now free to garbage-collect the { name: "Ana" } object,
// AND its entry in `metadata` is automatically removed along with it —
// no memory leak, no manual cleanup needed.If metadata had been a regular Map, the object would stay alive in memory indefinitely (still referenced by the Map), even after user = null.
2Key Restrictions (What Makes Them "Weak")
Map / Set | WeakMap / WeakSet | |
|---|---|---|
| Key/value types | any value | objects only (and, in modern engines, registered Symbols) as keys/members — no primitives |
| Iterable? | ✅ yes | ❌ no — no .keys(), no for...of, no .size |
| Can check size | ✅ .size | ❌ not available |
| Garbage collection | keeps entries alive | entries can be collected when the key is unreachable elsewhere |
const wm = new WeakMap();
wm.set("string key", 1); // TypeError: Invalid value used as weak map key — primitives not allowedThe lack of iteration isn't a missing feature — it's deliberate: since entries can disappear at any moment via garbage collection, allowing iteration would make behavior unpredictable and timing-dependent.
3Practical Use Case: Private Data Without #private Fields
Before class private fields (#field) existed, WeakMap was the standard technique for truly private, per-instance data that couldn't be reached from outside a module:
const privateData = new WeakMap();
class BankAccount {
constructor(balance) {
privateData.set(this, { balance });
}
deposit(amount) {
const data = privateData.get(this);
data.balance += amount;
return data.balance;
}
getBalance() {
return privateData.get(this).balance;
}
}privateData lives in module scope, inaccessible from outside; WeakMap ensures that when a BankAccount instance is garbage collected, its private data entry disappears with it automatically.
4Practical Use Case: Tagging Objects Without Modifying Them
const processedItems = new WeakSet();
function processOnce(item) {
if (processedItems.has(item)) return "already processed";
processedItems.add(item);
return "processing now";
}This tracks "have I seen this object before" without attaching a visible property to the object itself, and without leaking memory if the object is later discarded elsewhere in the program.
5DOM Node Metadata (Classic Browser Use Case)
const nodeData = new WeakMap();
function attachData(domNode, data) {
nodeData.set(domNode, data);
}
// If the DOM node is later removed from the page and has no other
// references, both the node AND its associated data are garbage
// collected together — no manual cleanup required.Under the HoodEphemerons — Why This Actually Requires Special GC Support▾
WeakMap/WeakSet aren't just "a Map/Set that happens to allow garbage collection" — supporting them correctly requires the garbage collector itself to understand a specific concept called an ephemeron, because naive weak-reference handling produces the wrong answer in a genuinely tricky case.
The problem a simple "weak pointer" doesn't solve
Imagine the GC just treated a WeakMap entry's key as a plain weak pointer (ignorable when deciding what's reachable) but treated the value as a normal strong reference. Now consider:
const wm = new WeakMap();
wm.set(keyObj, valueObj); // valueObj might itself be large, or reference OTHER objectsIf keyObj becomes unreachable everywhere else, the entry should disappear — including valueObj. But valueObj might be reachable through other paths too, or might itself hold references back toward things that matter. A correct implementation must decide "is valueObj needed?" based on "is keyObj reachable?" — a reachability question that depends on another object's reachability, not on valueObj being referenced directly. That's precisely what an ephemeron is: a (key, value) pair where the value's liveness is conditionally dependent on the key's liveness, not independently determined.
Why this needs a fixed-point algorithm, not a single pass
Object A --(strong ref)--> WeakMap entry: key = B, value = C
Object C --(strong ref)--> Object D
Is D reachable? → depends on whether C is kept → depends on whether B is reachable
(through some OTHR path entirely unrelated to this WeakMap)A garbage collector handling ephemerons correctly must run its reachability analysis to a fixed point: mark everything reachable through normal strong references first, then check each ephemeron's key — if the key turned out reachable, promote its value (and everything the value references) to reachable too, which might in turn make yet other ephemerons' keys newly relevant, requiring another pass. This repeats until nothing new becomes reachable. This is meaningfully more complex than simple mark-and-sweep, which is precisely why WeakMap/WeakSet couldn't just be "implemented in userland" efficiently before engines added real ephemeron support — no combination of ordinary weak references achieves this fixed-point semantics correctly on its own.
Why iteration is fundamentally incompatible, not just omitted for safety
Section 2 notes iteration is deliberately excluded. The ephemeron mechanics explain why it's not merely a design preference: an entry's continued existence depends on a garbage-collection decision that can happen at any point the engine chooses, following the fixed-point process above — there is no well-defined moment "between" GC passes where a stable, complete list of current entries could be produced without the act of listing them itself interfering with (or depending on) collection timing.
⚠️ Common Pitfalls
- Trying to use a primitive as a key — always throws
TypeError; only objects (and some Symbols in modern engines) are valid. - Expecting to iterate or check size — neither exists on
WeakMap/WeakSet, by design; if you need to enumerate entries, you need a regularMap/Setinstead (accepting the memory-retention tradeoff). - Using WeakMap when you actually need to enumerate later — if your use case requires listing all stored entries, WeakMap is the wrong tool regardless of its memory benefits.
- Assuming garbage collection timing is predictable/immediate — an entry disappears "eventually," not the instant the key becomes unreachable; never write logic that depends on exact GC timing.
📋 Quick Reference
WeakMap: object keys only, no iteration, no .size, entries GC'd with their key
WeakSet: object members only, no iteration, no .size, members GC'd when unreachable
Use for: private instance data, object tagging/metadata, avoiding memory leaks
Don't use for: anything you need to iterate, count, or serialize🎤 Interview Q&A
Q1What makes WeakMap and WeakSet 'weak'?core▾
They hold their keys (objects only) weakly: if no other references exist, the key and its value can be garbage-collected. They're not iterable and have no size, since contents can disappear anytime.
- Why can't keys be primitives? Primitives aren't collected like objects (non-registered symbols are now allowed).
Q2Give use cases for WeakMap.core▾
Associating private data or metadata with objects you don't own (DOM nodes, library objects) without preventing GC; caching computed results per object; implementing private fields before #private.
- Memoization example? Cache results keyed by input objects.
Q3Give use cases for WeakSet.core▾
Tagging objects, e.g. tracking which objects have been processed or visited (cycle detection in deep traversal) without leaking memory.
- Brand check? Mark instances created by a factory.
Q4What are WeakRef and FinalizationRegistry?senior▾
WeakRef holds a weak reference you can deref() (may return undefined after GC); FinalizationRegistry runs cleanup callbacks after objects are collected. Their timing is non-deterministic, so use sparingly.
- Use case? Caches of large objects that can be recreated.
Q5Why can't you iterate a WeakMap?core▾
Iteration results would depend on GC timing, making behaviour nondeterministic and exposing GC internals.
- Alternative if you need iteration? A Map with explicit cleanup.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.