Data structures
Object, Array, Map, Set, WeakMap, WeakSet. The interview question is almost never “what is a Map” — it is why you would choose one over a plain object, and that has real answers.
Map or object
const m = new Map();
m.set(userObj, "metadata"); // any value as a key
m.set(1, "number one");
m.get("1"); // undefined — 1 and "1" stay distinct
m.size; // O(1)| Object | Map | |
|---|---|---|
| Key types | string, symbol | anything, including objects |
| Key coercion | numbers become strings | none |
| Size | Object.keys(o).length |
.size |
| Order | integer-like keys first, then insertion | insertion, always |
| Inherited keys | yes, from the prototype | none |
| JSON | direct | needs conversion |
Reach for a Map when keys are dynamic, non-string, or added and removed often. Reach for an object when the shape is known, when it is a record you will serialise, or when you want the ergonomics of dot access.
The key-order rule surprises people:
const o = { b: 1, 2: 2, a: 3, 1: 4 };
// ["1", "2", "b", "a"] — integers sorted first
Object.keys(o);If insertion order matters, that alone is the reason to use a Map.
Gotcha: an object literal inherits from
Object.prototype, so a user-supplied key calledconstructoror__proto__is already “present”.Object.create(null)or aMapis the safe container for untrusted keys — this is the prototype-pollution shape in Frontend Security — Senior Interview Prep.
Set, and set operations
const s = new Set([1, 2, 2, 3]); // {1, 2, 3}
s.has(2); // O(1), vs O(n) for array.includes
[...new Set(items)]; // the dedupe idiomES2025 added real set methods, which replace the manual filter/spread versions:
a.union(b);
a.intersection(b);
a.difference(b);
a.symmetricDifference(b);
a.isSubsetOf(b);
a.isDisjointFrom(b);Membership is the point. Checking includes inside a loop is the classic
accidental O(n²); building a Set first makes it O(n).
Both Map and Set key by SameValueZero, so NaN equals NaN — which is why
new Set([NaN, NaN]).size is 1, and why Set finds a NaN that
indexOf cannot.
WeakMap and WeakSet: the memory story
Keys must be objects, and they are held weakly — an entry does not stop its key being garbage collected:
const meta = new WeakMap();
function attach(el, data) {
// no leak: when `el` goes, so does the entry
meta.set(el, data);
}With a normal Map, that same code keeps every element alive forever, because
the Map holds a strong reference. That is the entire use case: associating
data with an object you do not own the lifetime of — DOM nodes, class
instances, request objects.
The trade is that they are not enumerable and have no size. You cannot iterate
a WeakMap, because what it contains could change whenever the collector runs.
That is not a limitation to work around; it is what makes the guarantee
possible.
Stack and queue, without a class
Both are one line of array usage, and the reason to write them out is that one of them is a performance trap:
const stack = [];
stack.push(x); stack.pop(); // both O(1)
const queue = [];
// shift is O(n) — reindexes everything
queue.push(x); queue.shift();shift() on a large array moves every remaining element. For a queue of any
size, use two stacks or a head index:
class Queue {
#items = [];
#head = 0;
enqueue(x) { this.#items.push(x); }
dequeue() {
if (this.#head >= this.#items.length) return undefined;
const x = this.#items[this.#head];
// release the reference
this.#items[this.#head++] = undefined;
if (this.#head > 32 && this.#head * 2 > this.#items.length) {
// compact
this.#items = this.#items.slice(this.#head);
this.#head = 0;
}
return x;
}
}The = undefined matters: without it the array keeps a reference to every
dequeued item and the queue leaks. Compacting occasionally keeps the backing
array from growing without bound.
Choosing, quickly
| Need | Use |
|---|---|
| Fixed known shape, serialised | object |
| Dynamic keys, non-string keys, ordering | Map |
| Membership, dedupe | Set |
| Metadata keyed by an object you don’t own | WeakMap |
| LIFO | array push/pop |
| FIFO | array with a head index, not shift |
| Untrusted keys | Map or Object.create(null) |
Related
Interview angle 6
- “Map or object?” - Map for dynamic keys, non-string keys, frequent insertion and deletion, and guaranteed insertion order. Object for a known shape you will serialise. Objects coerce keys to strings and sort integer-like keys first, which surprises people relying on insertion order.
- “Why would you use
Object.create(null)?” - it has no prototype, so a user-supplied key likeconstructoror__proto__is not already present and cannot pollute anything. AMapgives the same safety, and both are the answer for untrusted keys. - “What is a WeakMap for?” - attaching data to an object whose lifetime you do not control, such as a DOM node. Keys are held weakly, so the entry disappears when the key is collected. A normal Map would keep every key alive forever.
- “Why can’t you iterate a WeakMap?” - because its contents can change whenever the garbage collector runs, so iteration order and size are not well defined. The lack of enumeration is what makes the weakness possible, not an oversight.
- “How do you implement a queue in JavaScript?” - not with
shift(), which is O(n) because it reindexes the whole array. Keep a head index and advance it, null out the slot you consumed so it can be collected, and compact occasionally. - “Why is
new Set([NaN, NaN]).sizeequal to 1?” - Map and Set key by SameValueZero, under whichNaNequals itself. That is also whySet.has(NaN)works whereArray.indexOf(NaN)returns-1.