Map and Set
Map for any-key dictionaries and Set for unique values — iteration order, object keys, Weak variants, and when objects/arrays still win.
- javascript
- map
- set
- collections
Plain objects and arrays cover a lot. Map and Set fix the sharp edges: object keys only stringify, accidental prototype keys, and slow “unique via indexOf” patterns. Prefer them when keys aren’t strings or when you need reliable collection semantics.
Map
const m = new Map();
m.set('n', 1);
m.set(1, 'one'); // number key ≠ '1'
m.set(true, 'yes');
const user = { id: 7 };
m.set(user, { roles: ['admin'] }); // object as key
m.get(user); // { roles: ['admin'] }
m.has('n'); // true
m.delete('n');
m.size; // count
const fromEntries = new Map([
['a', 1],
['b', 2],
]);
for (const [k, v] of fromEntries) {
// insertion order guaranteed
}
Map vs object
| Object | Map | |
|---|---|---|
| Keys | strings / symbols | any value |
| Size | manual | .size |
| Iterate | Object.* / for-in care |
for...of, .keys() |
| Prototype | can collide (toString) |
no accidental keys |
| JSON | natural | need conversion |
// object key footgun
const o = {};
o[user] = true; // key becomes "[object Object]"
// frequency map
function freq(str) {
const map = new Map();
for (const ch of str) map.set(ch, (map.get(ch) ?? 0) + 1);
return map;
}
Set
const s = new Set([1, 2, 2, 3]);
s.size; // 3
s.add(4);
s.has(2); // true
s.delete(2);
// unique array
const unique = [...new Set(ids)];
// membership for objects — reference equality
const seen = new Set();
seen.add(user);
seen.has(user); // true
seen.has({ id: 7 }); // false
// set operations (manual)
function union(a, b) {
return new Set([...a, ...b]);
}
function inter(a, b) {
return new Set([...a].filter((x) => b.has(x)));
}
function diff(a, b) {
return new Set([...a].filter((x) => !b.has(x)));
}
Modern engines also ship Set methods like union / intersection — check support or polyfill.
Iteration
map.keys();
map.values();
map.entries(); // default in for...of
set.values(); // same as keys for Set
// convert
Object.fromEntries(map); // string keys only really make sense
new Map(Object.entries(obj));
WeakMap / WeakSet (pointer)
Keys must be objects; entries don’t prevent GC. See WeakMap and WeakSet.
const wm = new WeakMap();
wm.set(element, { clicks: 0 });
When objects/arrays are still fine
- JSON-shaped config with string keys → object
- Ordered list with duplicates → array
- Map when: unknown key types, frequent add/delete, need size, no prototype risk
Interview answer
“Map is a key→value collection with any keys and insertion-ordered iteration; Set stores unique values by SameValueZero. I use Map for frequency counts and object keys, Set for uniqueness and membership. Objects remain fine for simple string-key records and JSON. WeakMap/WeakSet allow GC of keys.”
Related
Performance and key equality
// Map uses SameValueZero for keys — like Set
const m = new Map();
m.set(NaN, 'not-a-number');
m.get(NaN); // 'not-a-number'
// objects as keys are by reference
const a = { id: 1 };
const b = { id: 1 };
m.set(a, true);
m.has(b); // false
For “same user id” membership, store the id string/number in a Set, not the whole object, unless you control identity. Map/Set operations are amortized O(1); building a unique list via array.includes in a loop is O(n²). Prefer Set for dedupe of primitives on large inputs.
Further reading
Related guides
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- 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.
- 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.