JavaScript Map and Set Table
| API | What it does | Field note |
|---|---|---|
new Map() | Ordered key-value store | Any key type - objects, functions, NaN; Object keys are strings/symbols only |
map.set(k, v) | Add or replace | Chainable - returns the map |
map.get(k) / map.has(k) | Read / membership | Missing key returns undefined - indistinguishable from a stored undefined |
map.delete(k) / map.size | Remove / count | Real .size property, not Object.keys(x).length |
for (const [k, v] of map) | Iterate entries | Insertion order guaranteed; Object property order is mostly-but-not-always |
Object.fromEntries(map) | Map back to plain object | The bridge out; Object.entries(obj) is the bridge in |
new Set() | Unique-value collection | NaN deduplicates; objects compare by reference, not contents |
set.add(v) / set.has(v) | Add / membership | O(1) average - the fast membership test vs Array.includes O(n) |
[...new Set(arr)] | Array dedupe in one line | The idiomatic dedupe; order preserved |
set.intersection? / manual loop | Set algebra | No native union/intersection yet - loop the smaller set for has() checks |
new WeakMap() | Garbage-collectable keys | Object keys only, not iterable - the cache-without-leaks pattern |
[...new Set(arr)] is the one-liner everyone memorizes, but the deeper win is Map for keyed lookups that never touch JSON - and the Object.fromEntries bridge when serialization calls. Related tools: JS array methods table - split/join and filter are Set's constant dance partners - plus duplicate line remover for the no-code version of dedupe.Map and Set are the collections JavaScript should have shipped with from the start: a keyed store that accepts any key type, and a uniqueness container that answers have-I-seen-this in constant time. The table below is the working eleven - enough to replace every object-as-dictionary hack in a typical codebase.
Bottom line: the decision rule is about keys and lifecycle. Plain objects remain right for structured records with known string keys - especially anything that will be JSON-serialized. Map wins when keys arrive at runtime (request ids, DOM nodes, user input) because any value can be a key and insertion order is guaranteed. Set wins whenever the question is membership, not association.
The honest part: two behaviors surprise even experienced developers. Objects as Map keys compare by reference - two identical-looking objects are two different keys, which is either the feature (per-node metadata in a WeakMap) or the bug. And the one-line dedupe, [...new Set(arr)], preserves first-occurrence order but only for primitives - arrays of objects still need a keyed helper.
How to use
- Pick the container first: object for JSON-shaped records, Map for runtime keys, Set for membership tests.
- Use the API column as the translation guide - size instead of Object.keys().length, for...of entries instead of for...in.
- Bridge when serializing: Object.fromEntries(map) leaves Map world, Object.entries(obj) enters it.
Frequently asked questions
When should I use a Map instead of a plain object?
Three signals: keys are not known until runtime, keys are not strings (numbers, DOM nodes, even NaN work in a Map), or insertion order is load-bearing and guaranteed. Objects are still perfect for what they were always for - fixed-shape records that serialize to JSON. The hybrid pattern is common in real code: objects at the API boundary, Map inside for the hot lookup path, bridged with Object.entries and Object.fromEntries.
How does [...new Set(arr)] deduplicate an array?
A Set stores each value once - feeding it an array keeps the first occurrence of every distinct value in order, and spreading it back produces the deduplicated array. It is O(n) where a filter-and-indexOf approach is O(n squared). The honest limit: uniqueness is by SameValueZero, so it works for primitives including NaN (which equals itself here) but two objects with identical contents are still two distinct values - dedupe objects by mapping them through a key first, like [...new Map(arr.map(x => [x.id, x])).values()].
What makes WeakMap different from Map?
WeakMap keys must be objects and are held weakly: when nothing else references a key object, its entry becomes garbage-collectable. That makes WeakMap the cache-without-leaks - attach computed metadata to DOM nodes or library objects without keeping them alive. The trade: not iterable, no size, no clear - you can never enumerate what is inside, which is precisely the point.
Why is my Set of objects not deduplicating?
Because Set membership uses SameValueZero equality, and objects compare by reference - two separately-created objects with identical contents are two different values. Primitives dedupe as expected (including NaN, a rare case of NaN equaling NaN). For content-based dedupe, give the set a canonical key: a Map keyed by JSON.stringify of the sorted fields, or a string signature like id:brand for product rows.