Map vs Object — when do you use which?
Object: keys are strings/symbols, key order is mostly insertion (with integer-key quirks), prototype chain pollution. Map: any key type (objects, NaN), guaranteed insertion order, .size in O(1), better for frequent add/remove. Use Map for dictionaries; Object for shaped records.
Object and Map both store key-value pairs, but they have different design contracts. Junior code uses Object for everything; the senior signal is knowing when Map is the better choice and why.
Key differences.
| Feature | Object | Map |
|---|---|---|
| Key types | string, symbol | anything (objects, functions, NaN, primitives) |
| Iteration order | insertion (integer keys sort numerically first) | guaranteed insertion |
| Size | Object.keys(o).length — O(n) | map.size — O(1) |
| Prototype | inherits from Object.prototype (__proto__, constructor, toString...) | none |
| Iteration | requires Object.keys/entries/values | directly iterable (for…of, forEach) |
| JSON | JSON.stringify(obj) works | not directly serializable — convert to array of pairs |
| Performance | optimized by V8 for "shape-stable" records | optimized for frequent insert/delete |
| Memory | smaller per entry for small objects | larger overhead per entry |
When to use Map.
- Dictionaries with arbitrary keys — especially keys you don't control (user-provided strings could collide with
__proto__,constructor). - Object keys — caching/memoization keyed by an object reference, WeakMap for the same with GC.
- Frequent add/remove — Map is implemented for this; Object reshaping triggers V8 deopts.
- Insertion-order matters always — Object's integer-key reordering surprises people:
{2:"a", 1:"b"}iterates1,2. - You need .size cheaply — Map gives it in O(1).
When to use Object.
- Records with a known shape —
{ id, name, email }. V8 generates hidden classes; access is faster. - JSON I/O — APIs and storage round-trip Object naturally.
- Function arguments / config bags — destructuring works directly.
- Shorthand syntax —
{ a, b }is more concise thannew Map([["a", a], ["b", b]]).
The prototype-pollution gotcha.
const cache = {};
cache["__proto__"] = "owned"; // doesn't actually set the key — modifies prototype
cache["hasOwnProperty"] = false; // breaks future calls if you do cache.hasOwnProperty(k)Defenses: Object.create(null) (no prototype), Object.hasOwn(obj, key) (modern), or just use Map which has no prototype.
Iteration:
// Map (clean)
for (const [k, v] of map) { ... }
// Object (need to choose what you want)
for (const k of Object.keys(obj)) { ... }
for (const [k, v] of Object.entries(obj)) { ... }Performance reality. For 100k entries with frequent add/delete, Map is meaningfully faster than Object. For 100 stable-shape records, Object wins on memory and access. Don't optimize without a profile.
Sets vs Map. Set is the value-only equivalent of Map. Use it for "is this in the collection?" — replaces ad-hoc { [key]: true } patterns.
Weak variants. WeakMap / WeakSet hold object keys weakly so they can be garbage-collected. Useful for: tagging DOM elements without leaking when removed, caches keyed by component instance, private-data patterns. Trade-off: not iterable, no .size.
Decision shortcut.
- Plain record? →
Object. - Hash table / dictionary with dynamic keys? →
Map. - Object as key, want GC? →
WeakMap. - Set membership? →
Set.
Code
Follow-up questions
- •Why does Object reorder integer keys?
- •What's the use case for WeakMap?
- •How would you make a 'safe' Object dictionary?
- •When would you serialize a Map to JSON?
Common mistakes
- •Using Object for user-provided keys → prototype pollution.
- •Reaching for Object.keys(obj).length in a hot loop instead of tracking size.
- •Trying to use a Date or Object as an Object key — coerces to '[object Object]'.
- •Forgetting Map iteration is direct — wrapping in Object.entries unnecessarily.
Performance considerations
- •V8 optimizes stable-shape Objects (hidden classes); reshaping triggers deopts.
- •Map outperforms Object for dynamic-key workloads at scale.
- •WeakMap avoids leaks when keying by long-lived objects.
Edge cases
- •NaN as a Map key works once (NaN === NaN inside Map); Object coerces NaN to 'NaN' string.
- •Map preserves insertion order including numeric keys — Object does not.
- •JSON.stringify(map) → '{}' — convert to entries first.
Real-world examples
- •React internals use Map/WeakMap for keyed children and component state caches; React Router uses Map for route params.