Suggest an editImprove this articleRefine the answer for “Set, Map, WeakSet and WeakMap in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Set, Map, WeakSet and WeakMap** are JavaScript's four built-in collection types. Set stores unique values. Map stores key-value pairs with any key type. WeakSet and WeakMap use weak references, so entries are garbage collected automatically when keys have no other references. ```javascript const set = new Set([1, 2, 2]); // Set {1, 2} const map = new Map([['key', 1]]); let obj = {}; const wm = new WeakMap(); wm.set(obj, 'data'); obj = null; // entry GC'd automatically ``` **Key:** Set/Map hold strong references and support iteration; WeakSet/WeakMap trade iteration for automatic memory cleanup.Shown above the full answer for quick recall.Answer (EN)Image**Set, Map, WeakSet and WeakMap** are four built-in JavaScript collection types that each solve a different storage problem. ## Theory ### TL;DR - Set is a list that never holds duplicates. Map stores key-value pairs where keys can be objects, not just strings. - WeakSet and WeakMap hold weak references to objects, so the garbage collector removes entries when nothing else points to those objects. - Main split: Set/Map keep strong references and support iteration. WeakSet/WeakMap trade iteration for automatic memory cleanup. - Use Set for deduplication, Map when you need non-string keys, WeakMap for DOM node metadata or caches. ### Quick example ```javascript const set = new Set([1, 2, 2, 3]); // Set {1, 2, 3} - duplicates dropped const map = new Map(); map.set('name', 'Alice'); map.set({id: 1}, 'object as key'); // any type works as key let node = document.querySelector('#btn'); const weakMap = new WeakMap(); weakMap.set(node, { clickCount: 0 }); // attach data to DOM node node = null; // entry will be GC'd automatically ``` Set drops the duplicate `2` on creation. Map accepts an object as a key, which plain objects cannot do. WeakMap ties data to a DOM node and releases it when the node is gone. ### Key difference Set and Map hold strong references. An object stored as a Map key stays in memory as long as that Map exists, even if no other code references it. WeakSet and WeakMap use weak references: if the only pointer to an object is a WeakMap key, the garbage collector treats that object as unreachable and removes the entry. No manual cleanup needed. ### When to use - Remove duplicates or check membership quickly → Set (O(1) lookup vs array's O(n)) - Store values by non-string keys, like objects or Symbols → Map - Track per-element state for DOM nodes without memory leaks → WeakMap - Flag which objects have been visited in an algorithm, like cycle detection → WeakSet - Need to iterate or check `.size` on a weak collection → not possible, use Set/Map instead ### Comparison table | Feature | Set | Map | WeakSet | WeakMap | |---|---|---|---|---| | Stored data | Values only | Key-value pairs | Objects only | Object keys, any values | | Key types | Any value | Any value | Objects only | Objects only | | Iteration | Yes | Yes | No | No | | `.size` property | Yes | Yes | No | No | | Garbage collection | No (strong refs) | No (strong refs) | Yes (weak refs) | Yes (weak refs) | | Typical use | Unique lists | Non-string key maps | Visited object flags | Node caches, metadata | ### How it works internally V8 implements Set and Map as hash tables with an ordered linked list on top, which is why insertion order is preserved during iteration. Equality uses SameValueZero rather than `===`. The practical difference: `NaN === NaN` is false in JavaScript, but SameValueZero treats two `NaN` values as identical, so `new Set([NaN, NaN])` gives `Set {NaN}` with one element. WeakSet and WeakMap use a structure called an ephemeron table. An ephemeron is a key-value pair where the value is kept alive only if the key is reachable from outside the table. During a GC cycle, if the engine finds a WeakMap key has no external references, the entry is dropped. Iteration is excluded by design: iterating would require holding strong references to all keys temporarily, which blocks the garbage collector. ### Common mistakes **Using object literals as Map keys and expecting them to match:** ```javascript const map = new Map(); map.set({ id: 1 }, 'data'); console.log(map.has({ id: 1 })); // false - different object reference ``` Map compares keys by reference (SameValueZero), not deep equality. Two `{id: 1}` objects are distinct in memory. Store the reference in a variable if you need to retrieve the value later. **Expecting `.size` or iteration on WeakMap or WeakSet:** ```javascript const wm = new WeakMap(); wm.set({}, 1); console.log(wm.size); // undefined for (const [k, v] of wm) {} // TypeError: wm is not iterable ``` There is no `.size` and no way to list entries. If you need a count, track it separately. **Assuming WeakMap prevents all memory leaks automatically:** ```javascript let obj = { data: 'large payload' }; const cache = new WeakMap(); cache.set(obj, 'computed result'); // obj is still referenced above - no GC happens yet // Only when ALL strong refs to obj are gone will the WeakMap entry be cleaned up ``` Weak references work only when nothing else holds the object. A closure, an array, or another variable still pointing to `obj` keeps the WeakMap entry alive. **Using a plain object instead of Map for non-string keys:** Plain objects coerce all keys to strings. `obj[{id:1}]` becomes `obj["[object Object]"]`. Map keeps the actual type, so `map.set({id:1}, value)` stores the object reference without any coercion. ### Real-world usage - **React** uses WeakMap in its Fiber reconciler to associate effects with fiber nodes without blocking their collection. - **Node.js AsyncHooks** uses WeakMap to track async resources so finished operations can be GC'd. - **Lodash** uses Set inside `_.uniq` for O(n) array deduplication. - **Preact** uses WeakSet to track which hooks have been committed. - **Express** uses Map for middleware parameter registries where insertion order matters. ### Follow-up questions **Q:** What is SameValueZero and how does it differ from `===`? **A:** SameValueZero treats `+0` and `-0` as equal, and `NaN` as equal to `NaN`. Regular `===` returns false for `NaN === NaN`. So `new Set([NaN, NaN])` gives `Set {NaN}` with one element. **Q:** Why can't you iterate over WeakSet or WeakMap? **A:** Iteration requires the engine to enumerate all keys, which means holding strong references to them during the loop. That blocks garbage collection, so the design deliberately excludes iteration. **Q:** Can WeakMap keys be primitives like strings or numbers? **A:** No. Primitives are values, not references. GC tracks object references, so only objects can be weak keys. Passing a string throws a TypeError. **Q:** Set `.has()` vs `Array.includes()` - is there a real performance difference? **A:** Set uses a hash table, so `.has()` is O(1) on average. `Array.includes()` scans linearly, O(n). For large collections or repeated lookups, Set is noticeably faster. **Q (senior level):** How would you implement an LRU cache using WeakMap? **A:** WeakMap handles the key-to-value association and automatic cleanup when keys are GC'd. You still need a doubly-linked list or an access-ordered Map to track eviction order, because WeakMap has no iteration. On each access, move the entry to the front. On capacity overflow, evict the tail. If a key gets GC'd externally, its entry disappears from WeakMap without touching the linked list. ## Examples ### Deduplication and membership check with Set ```javascript const visited = new Set(); function processPage(url) { if (visited.has(url)) { console.log('Already processed:', url); return; } visited.add(url); console.log('Processing:', url); } processPage('https://example.com'); // Processing: https://example.com processPage('https://example.com'); // Already processed: https://example.com processPage('https://other.com'); // Processing: https://other.com console.log(visited.size); // 2 ``` Set replaces the typical `if (array.indexOf(url) !== -1)` pattern and cuts lookup time to O(1). I used this exact pattern for deduplicating navigation history in a single-page app - switching from an array to Set cut lookup time on large histories from noticeable to instant. ### Map with object keys for per-request context ```javascript const requestContext = new Map(); function handleRequest(req) { requestContext.set(req, { startTime: Date.now(), userId: req.headers['x-user-id'] }); } function logRequest(req) { const ctx = requestContext.get(req); console.log(`Duration: ${Date.now() - ctx.startTime}ms`); requestContext.delete(req); } ``` With a plain object, `req` as a key would become `"[object Object]"` and every request would overwrite the same entry. Map preserves the reference and keeps each request's context separate. ### WeakMap for DOM node state without memory leaks ```javascript const clickCounts = new WeakMap(); document.querySelectorAll('button').forEach(button => { clickCounts.set(button, 0); button.addEventListener('click', () => { const count = (clickCounts.get(button) || 0) + 1; clickCounts.set(button, count); console.log(`Clicked ${count} times`); }); }); // When a button is removed from the DOM and no other references exist, // its entry in clickCounts is automatically cleaned up by the garbage collector ``` With a regular Map here, every removed button would stay in memory as long as the Map lived. WeakMap makes the cleanup automatic.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.