JavaScript · Core language
`Map` / `Set` vs objects/arrays
⚡ TL;DR — Use
Mapwhen keys aren't strings or you need ordered, size-aware, frequently-mutated key/value storage; useSetfor uniqueness and O(1) membership. Objects are records with a known shape; arrays are ordered lists. Reaching for{}as a general-purpose hash map is the default that quietly bites.
🧠 Mental model
Objects and arrays are structures with meaning — a user record, a list of line-items. Map and Set are collections — general-purpose containers built for dynamic membership and lookup. The senior signal is knowing that {} makes a bad dictionary: string-only keys, inherited prototype keys, no .size, and property ordering that silently reshuffles numeric keys.
| Object | Map | |
|---|---|---|
| Keys | strings / symbols only | any value — objects, functions, NaN |
| Size | Object.keys(o).length |
.size (O(1)) |
| Iteration | via Object.entries; own+enumerable |
directly iterable, insertion order |
| Prototype | inherits (toString, __proto__) |
no default keys |
| Best at | fixed shape, JIT hidden classes, JSON | frequent add/delete, lookup |
⚙️ How it actually works
Map/Set compare keys with SameValueZero — so NaN equals NaN and +0/-0 are the same, and object identity is respected (two different objects are two different keys). Object keys, by contrast, are stringified: 1 and '1' collide, and any object key becomes '[object Object]', so all objects map to one slot. Object property order also puts integer-like keys first in ascending order, then string keys in insertion order — Map is pure insertion order, always. WeakMap/WeakSet take only object keys, aren't iterable, and don't prevent garbage collection — ideal for per-object metadata.
💻 Code
// ❌ object as a map: keys stringify, collisions, inherited keys
const seen = {};
seen[1] = 'a';
seen['1']; // 'a' — 1 and '1' are the SAME key
'toString' in seen; // true — inherited, a false positive
// ✅ Map: real keys, honest membership
const m = new Map();
const key = { id: 1 };
m.set(key, 'meta');
m.get(key); // 'meta' (object identity as key)
m.has('toString'); // false
m.size; // 1
// Set for dedupe + O(1) membership
const unique = [...new Set([1, 1, 2, 3])]; // [1, 2, 3]
// WeakMap: GC-friendly cache keyed by an object — no leak
const cache = new WeakMap();
cache.set(domNode, data); // entry disappears when domNode is collected
⚖️ Trade-offs
- Object wins for fixed-shape records (V8 optimises stable shapes into hidden classes), for JSON serialization, and for literal ergonomics.
- Map wins for dynamic dictionaries, non-string keys, frequent add/delete, guaranteed order, and O(1)
.size. - Set replaces
array.includes(O(n)) with O(1) membership — a real win in hot loops and dedup. - WeakMap/WeakSet for metadata keyed by objects without leaking — but you trade away iteration and size by design.
- Don't Map everything. For small fixed data that has to serialize, a plain object is lighter and freer.
💣 Gotchas interviewers probe
- Object keys are strings/symbols.
obj[1] === obj['1'], and every object key collapses to'[object Object]'— so objects can't be distinct keys in a plain object. - Prototype pollution.
'toString' in objistrue; assigningobj['__proto__']is dangerous.Object.create(null)orMapavoids it. - Integer-like keys reorder.
{ 2:'a', 1:'b' }iterates1then2— insertion order is lost.Mappreserves it. Map/Setare not JSON-serializable.JSON.stringify(new Map())yields{}; convert with[...map].WeakMapcan't be iterated or sized — deliberate, and its keys must be objects.Object.keys(map)returns[]— aMap's entries aren't own properties; iterate the Map directly or spread it.NaNhandling differs. AMapcan key onNaN;array.indexOf(NaN)is-1whilearray.includes(NaN)istrue.
🎯 Say this in the interview
"I split them by intent: objects and arrays are structures with meaning — a record, a list — while
MapandSetare general collections. My default rule is: the moment keys aren't fixed strings, or I need frequent add/delete, ordered iteration, or a real size, I use aMap. A plain object is a poor dictionary because keys are stringified —1and'1'collide, every object key becomes'[object Object]'— and it inherits prototype keys, so'toString' in objis a false positive.Mapuses SameValueZero and preserves insertion order, which objects don't for integer-like keys.Setgives me O(1) membership instead ofincludes's O(n). AndWeakMapis my go-to for caching data against an object without leaking memory, since entries are collected with their keys."
🔗 Go deeper
- javascript.info — Map and Set — key equality, iteration order, and when to choose which.
- MDN —
Map— full API and the object-vs-Map comparison table. - MDN —
Set— uniqueness and membership semantics. - MDN —
WeakMap— GC-friendly keying and its deliberate limitations.