Suggest an editImprove this articleRefine the answer for “What are WeakMap, WeakSet, and WeakRef?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**WeakMap, WeakSet, and WeakRef** hold weak references to objects, letting the garbage collector reclaim them automatically when no strong references exist elsewhere. Unlike regular `Map` and `Set`, weak versions never block GC. ```javascript const cache = new WeakMap(); let node = document.querySelector('#btn'); cache.set(node, { color: 'red' }); node = null; // node is now eligible for GC; the cache entry goes with it ``` **Key:** weak references track objects without owning them.Shown above the full answer for quick recall.Answer (EN)Image**WeakMap, WeakSet, and WeakRef** hold weak references to objects, so the JavaScript garbage collector can automatically reclaim those objects when nothing else points to them. ## Theory ### TL;DR - Regular `Map`/`Set` keep objects alive as long as they exist; weak versions let the GC collect them freely - Analogy: a party guest list where names erase themselves when guests leave, no cleanup code needed - `WeakMap` stores key-value pairs (keys must be objects); `WeakSet` stores unique objects; `WeakRef` wraps one object with manual `.deref()` access - None of the three support iteration, `.size`, or `.clear()`, and that is by design - Use weak versions when object lifetime and data lifetime differ (DOM caches, event handler tracking) ### Quick example ```javascript // Map holds obj in memory even after obj = null const strongMap = new Map(); let obj = { data: 'example' }; strongMap.set(obj, 'value'); obj = null; // Map still holds the original object - memory stays allocated // WeakMap releases it const weakMap = new WeakMap(); let obj2 = { data: 'example' }; weakMap.set(obj2, 'value'); obj2 = null; // GC can now reclaim obj2, and the WeakMap entry disappears with it ``` After `obj2 = null`, no strong references remain. The GC reclaims the object, and the WeakMap entry goes with it automatically. ### Key difference Regular `Map` and `Set` hold strong references. The GC sees "something points to this object" and leaves it alone. Weak versions tell the GC: "I know about this object, but don't keep it alive for me." When the object has no other strong references, GC collects it and removes the weak entry. This is what stops DOM caches and event handler registries from leaking memory over time. ### WeakMap `WeakMap` is a key-value store where keys must be objects. Values can be anything. The API is small on purpose: `set`, `get`, `has`, `delete`. No `.size`. No iteration. No `.keys()`. Why no iteration? To give you a list of keys, JavaScript would need to hold strong references to all of them. That defeats the whole point. ### WeakSet `WeakSet` stores unique objects with the same GC behavior. You get `add`, `has`, `delete`. That is the entire API. The typical use case is marking objects as "visited" or "processed" without holding them in memory indefinitely. ### WeakRef `WeakRef` wraps a single object and exposes it through `.deref()`. Unlike `WeakMap`, there is no automatic side-effect cleanup. It just gives you a reference that does not block GC. Once the object is collected, `.deref()` returns `undefined`. Check the return value every time before using it. ```javascript let target = { id: 42 }; const ref = new WeakRef(target); target = null; // some time passes... const obj = ref.deref(); if (obj) { console.log(obj.id); // safe } else { console.log('already collected'); } ``` ### When to use - Temporary DOM node caches where nodes get unmounted: `WeakMap` (node as key, computed data as value) - Tracking which objects have been processed without owning them: `WeakSet` - Optional caching where reuse is desirable but not required: `WeakRef` with `.deref()` check - Permanent data that must survive regardless: plain `Map` or `Set` - Primitive keys (strings, numbers): plain `Map`, since WeakMap only accepts objects ### Comparison table | Feature | Map / Set | WeakMap / WeakSet / WeakRef | |---|---|---| | Key / value types | Any (primitives + objects) | Objects only | | GC behavior | Strong ref, blocks GC | Weak ref, allows GC | | Iterable | Yes (keys, values, entries) | No | | `.size` | Yes | No | | `.clear()` | Yes | No | | When to use | Persistent data storage | Ephemeral tracking (DOM caches, request metadata) | ### How the engine handles this V8 (Chrome and Node.js) uses mark-sweep GC. During the mark phase, it follows strong references. Objects with no incoming strong references get swept. WeakMap entries use an internal structure called "ephemeron": the entry survives only if its key survives the mark phase. WeakRef works the same way but requires a manual `.deref()` call. Node.js 14+ and Chrome 84+ support all three. ### Common mistakes **1. Expecting `.size` on WeakMap** ```javascript const wm = new WeakMap(); wm.set({}, 1); console.log(wm.size); // undefined - no error, just no property ``` There is no `.size`. Track counts with a separate `Map` if you need them. **2. Using a primitive as a WeakMap key** ```javascript const wm = new WeakMap(); wm.set('key', 1); // TypeError: Invalid value used as weak map key ``` WeakMap only takes objects. Wrap the primitive in an object, or use a plain `Map`. **3. Treating WeakRef as guaranteed storage** ```javascript const ref = new WeakRef({ data: 'important' }); // ... later, no strong reference kept ... ref.deref().data; // may throw - deref() can return undefined ``` Always pattern-match: `const obj = ref.deref(); if (obj) { use(obj); }`. **4. Adding primitives to WeakSet** ```javascript const ws = new WeakSet(); ws.add(42); // TypeError: Invalid value used in weak set ``` WeakSet is for object identity tracking. Use a plain `Set` for primitives. **5. Trying to iterate WeakMap** ```javascript const wm = new WeakMap(); for (const [k, v] of wm) { } // TypeError: wm is not iterable ``` Weak collections are intentionally non-iterable. Switch to `Map` if you need to loop. ### Real-world usage - `react-window` uses `WeakMap` to cache virtualized row heights with DOM nodes as keys; when nodes unmount, cache entries clean up automatically - Node.js `http` module uses `WeakMap` for request metadata tracking without leaking on aborted requests - Lodash `_.memoize` supports WeakMap-based caching for object arguments - Preact Signals use `WeakRef` for detached observers so they do not block component cleanup ### Follow-up questions **Q:** What does `let m = new WeakMap(); let o = {}; m.set(o, 1); o = null; console.log(m.has(o));` output? **A:** `false`. After `o = null`, the variable holds `null`. So `m.has(null)` is false. The original object is now eligible for GC. **Q:** Why can't you iterate a WeakMap? **A:** Iteration requires holding strong references to all keys at once. That would prevent GC from collecting them, which is exactly what WeakMap is supposed to avoid. **Q:** When would you pick WeakRef over WeakMap? **A:** When you have one object and no associated value to store. WeakMap is for key-value pairs tied to an object's lifetime. WeakRef is for "I want to check if this object is still around." **Q:** Can GC timing vary between Node.js and the browser? **A:** Yes. Node.js V8 tends to be more aggressive with GC cycles. Browsers throttle GC for responsiveness. Do not rely on `.deref()` returning a value for any specific duration. **Q:** (Senior) How would you build a leak-proof cache that also tracks entry count? **A:** Use a `WeakMap` for the cache (objects released automatically) plus a plain `Map` for the count, incrementing on `set` and decrementing on known deletes. The WeakMap handles GC; the counter handles accounting. The trade-off: when GC collects entries you did not explicitly delete, the count drifts. That is accepted behavior for this pattern. ## Examples ### Basic: DOM node cache ```javascript const styleCache = new WeakMap(); function getComputedColor(node) { if (!styleCache.has(node)) { // getComputedStyle is an expensive DOM call - cache the result styleCache.set(node, window.getComputedStyle(node).color); } return styleCache.get(node); } const button = document.querySelector('#submit'); console.log(getComputedColor(button)); // 'rgb(0, 0, 0)' // When button is removed from the DOM and no code holds a reference to it, // GC collects the node and styleCache drops the entry automatically. ``` No cleanup code needed. Working with a virtualized list once, I swapped a plain `Map` cache for `WeakMap` in a row height calculator. Memory in Chrome DevTools dropped visibly after rapid row mounts and unmounts. The old `Map` had been accumulating every detached row node without releasing anything. ### Intermediate: Tracking processed requests with WeakSet ```javascript const processedRequests = new WeakSet(); async function handleRequest(req) { if (processedRequests.has(req)) { return; // already handled, skip } processedRequests.add(req); const data = await fetch(req.url).then(r => r.json()); console.log('Processed:', data); // When req goes out of scope, WeakSet releases it automatically. // A plain Set would hold every request object for the server's entire lifetime. } ``` The key point: you never call `processedRequests.delete(req)`. The GC handles it when the request object is no longer referenced anywhere else. ### Advanced: Optional image cache with WeakRef ```javascript class ImageLoader { #cache = new Map(); // url -> WeakRef of the img element load(url) { const existing = this.#cache.get(url); if (existing) { const img = existing.deref(); if (img) { return img; // still alive, reuse it } this.#cache.delete(url); // stale entry, clean it up } const img = new Image(); img.src = url; this.#cache.set(url, new WeakRef(img)); return img; } } const loader = new ImageLoader(); const img1 = loader.load('https://example.com/photo.jpg'); // loads fresh const img2 = loader.load('https://example.com/photo.jpg'); // reuses img1 if still alive ``` When the browser needs memory, it can collect image elements. The `.deref()` check handles both cases without crashing.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.