JavaScript Built-In Data Structures for Coding Interviews

May 29, 202610 min read
dsaalgorithmsinterview-prepleetcode
TL;DR
  • Array.shift() is O(n) — use a head pointer for BFS queues or you turn O(V+E) into O(V²+E)
  • Array.sort() is lexicographic by default — always pass (a, b) => a - b when sorting numbers
  • Map beats Object for dynamic keys: any key type, O(1) .size, and no prototype footgun
  • Set gives O(1) has, add, and delete — the right structure for visited tracking in BFS/DFS
  • JavaScript ships with no native heap — memorize a 30-line MinHeap class before Dijkstra or any top-K problem
  • NaN !== NaN in JavaScript — use Number.isNaN(), not the global isNaN()
  • var closures in loops share one binding — always use let to avoid the classic prints-N bug

JavaScript gives you four native data structures that matter in a coding interview: Array, Object, Map, and Set. Two of them will silently wreck your runtime. One will sort your numbers in a way that is technically correct and completely wrong. None of them are a heap. Here is exactly what each one costs and when to reach for it, so you stop finding out at minute 30 of a live interview.

Your Stack and Your Array Are the Same Thing

Array is the workhorse. push and pop are O(1). That makes Array a perfectly good stack, and most candidates use it that way without thinking twice. So far so good.

The trap is the other end. shift (remove from front) and unshift (insert at front) are both O(n), because every remaining element in the backing buffer has to be re-indexed. For stack operations, this never matters. For queues, it is a disaster in slow motion.

BFS over a graph with V vertices and E edges should be O(V+E). Using shift inside the loop makes it O(V² + E). At V = 10,000, that is a 10,000x slowdown. The algorithm looks correct. It passes on the small examples. And then the interviewer says "what happens on a large graph?" and you have a very long 20 seconds.

The fix is a head pointer:

const queue = []; let head = 0; queue.push(start); while (head < queue.length) { const node = queue[head++]; for (const neighbor of adj[node]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } }

The array grows right, head slides forward, and nothing shifts. You keep the full array in memory but avoid the quadratic behavior. Two extra lines of code for a 10,000x speedup. Worth it.

Two more Array methods with non-obvious complexity:

  • indexOf and includes are O(n). If you are calling them inside a loop, use a Set instead.
  • splice (insert or remove at index i) is O(n) because of shifting.

sort() Will Ruin Your Numbers Without a Word

Array.prototype.sort() converts elements to strings before comparing them unless you pass a comparator. This is technically spec-compliant. It is also responsible for more interview bugs than almost anything else in the language.

[10, 9, 2].sort() // [10, 2, 9], lexicographic, wrong [10, 9, 2].sort((a, b) => a - b) // [2, 9, 10], numeric, correct

"10" comes before "2" alphabetically. The sort is working as designed. It just is not working how you want. Always pass a comparator when sorting numbers. No exceptions, no "I'll remember this time."

One thing that has improved: Array.prototype.sort has been stable since Chrome 70 and V8 v7.0, because the ECMAScript 2019 specification mandated it. In practice that means Timsort for most engines. You can rely on equal elements staying in their original relative order.

Object Is a Record, Not a Hash Map

Object works for fixed-shape data where you know the keys at write time. Config objects, coordinate pairs, tree nodes. It is not the right tool when keys are dynamic or unknown, and JavaScript will not complain when you use it wrong.

Two traps appear constantly in interview code:

The prototype footgun. Object inherits from Object.prototype, which means keys like "constructor", "toString", and "hasOwnProperty" already exist on every plain object. If your input includes those strings as keys, your lookup returns a function instead of undefined. This is the kind of bug that passes your tests and then detonates in the interview.

const freq = {}; freq["constructor"]; // [Function: Object], not undefined

The safe guard is Object.create(null), which makes an object with no prototype. In interviews, using Map sidesteps this entirely.

Keys are always strings. obj[1] and obj["1"] access the same slot. Numbers get coerced. This usually does not matter, but it matters for problems where you are mapping objects or arrays as keys, which will not work at all with Object. Every object stringifies to "[object Object]". You'll have one key. It will be wrong.

Map Is the Hash Map You Actually Want

For anything resembling a frequency counter, memoization table, or adjacency list, use Map.

Map gives you O(1) get, set, has, and delete with any key type, including numbers, objects, arrays, NaN, and null.

const map = new Map(); map.set([1, 2, 3], "array key"); // works map.set(NaN, "nan key"); // works, and NaN === NaN here map.set(null, "null key"); // works map.has(NaN); // true map.size; // 3, O(1), no Object.keys() overhead

The .size property is worth noting. Object.keys(obj).length is O(n). map.size is O(1). In a tight loop, that difference adds up.

Map also preserves insertion order during iteration, which Object does not guarantee for integer-like string keys (engines sort numeric keys first).

The one case where Object wins: terse syntax for a fixed set of string keys you know at write time.

// Object makes sense here const node = { val: 5, left: null, right: null }; // Map makes sense here const freq = new Map(); for (const char of s) freq.set(char, (freq.get(char) ?? 0) + 1);

Note ?? (nullish coalescing) instead of ||. Zero is falsy, so 0 || 1 evaluates to 1. Miss that and your first character's count jumps straight to 1. You will not notice until you run it on the string "aaa" and wonder why the answer is off by one.

Set Is O(1) Membership. Use It for Visited Tracking.

Set stores unique values and gives you O(1) add, has, and delete. The dominant interview use case is tracking visited nodes in BFS or DFS.

const visited = new Set(); visited.add(node); if (visited.has(node)) { /* skip */ }

Set also deduplicates in one line: [...new Set(arr)].

One thing to avoid in 2026: ES2025 added native set operations including union, intersection, and difference. They are clean. They are also not available on LeetCode, which runs Node 18. Writing setA.union(setB) will throw. Implement manually if you need it.

The Structures JavaScript Does Not Give You

JavaScript ships with no heap, no sorted map, no deque. Go has a heap. Python has a heap. Java has a heap. JavaScript has Array.prototype.sort, which you now know sorts by string comparison by default. These gaps matter for a real fraction of interview problems, and the heap gap in particular catches people completely off guard.

Heap (priority queue). Any top-K problem, Dijkstra's algorithm, or meeting room scheduling problem wants a heap. You have to bring your own. This is not a bug or an oversight by the language designers. It is just how it is. A 30-line binary min-heap with a comparator function covers most cases:

class MinHeap { constructor(compareFn = (a, b) => a - b) { this.heap = []; this.cmp = compareFn; } push(val) { this.heap.push(val); let i = this.heap.length - 1; while (i > 0) { const p = (i - 1) >> 1; if (this.cmp(this.heap[i], this.heap[p]) < 0) { [this.heap[i], this.heap[p]] = [this.heap[p], this.heap[i]]; i = p; } else break; } } pop() { const top = this.heap[0]; const last = this.heap.pop(); if (this.heap.length > 0) { this.heap[0] = last; let i = 0, n = this.heap.length; while (true) { let s = i, l = 2*i+1, r = 2*i+2; if (l < n && this.cmp(this.heap[l], this.heap[s]) < 0) s = l; if (r < n && this.cmp(this.heap[r], this.heap[s]) < 0) s = r; if (s === i) break; [this.heap[i], this.heap[s]] = [this.heap[s], this.heap[i]]; i = s; } } return top; } peek() { return this.heap[0]; } size() { return this.heap.length; } } // Max-heap: flip the comparator const maxHeap = new MinHeap((a, b) => b - a);

Sorted structure (TreeMap). When a problem needs "find all elements less than X" or "find the closest value to K," reach for a sorted array plus binary search. It is O(n) insert (because of shifting) but the query side is O(log n), which is often acceptable for the problem sizes you see in interviews.

In SpaceComplexity mock interviews, Dijkstra and top-K problems catch more JavaScript candidates than almost anything else. The heap is not there by default, and candidates freeze on the implementation under time pressure. "Wait, JavaScript doesn't have a PriorityQueue?" is not a question you want to be asking out loud at minute 15. Having this template memorized saves you ten minutes.

JavaScript Data Structures: Quick Reference

StructureAccessInsertDeleteNotes
Array (index)O(1)O(1) push / O(n) unshiftO(1) pop / O(n) shiftsort() needs comparator for numbers
ObjectO(1)O(1)O(1)prototype footgun, string keys only
MapO(1)O(1)O(1)any key type, .size is O(1)
SetO(1)O(1)O(1)unique values, use for visited tracking
MinHeap (manual)O(1) peekO(log n) pushO(log n) popbring your own implementation

Four Traps That Actually Come Up

1. NaN comparisons. NaN !== NaN in JavaScript. Not a typo. Not a mistake. IEEE 754 requires it. The global isNaN() coerces its argument first (isNaN("hello") returns true, which is useless). Use Number.isNaN().

2. typeof null === "object". This catches everyone once. Exactly once, because after you stare at a null-pointer-equivalent crash in JavaScript for 20 minutes, you never forget. When you are checking whether a tree node exists, if (node) is safer than if (typeof node === "object").

3. No integer type. JavaScript numbers are IEEE 754 float64. They are exact up to 2^53 - 1 (about 9 quadrillion, which is plenty). For problems involving modular arithmetic near 10^18, you need BigInt or careful reduction.

4. var in loops. If you push closures into an array inside a for (var i ...) loop, all closures share one binding for i. By the time they run, i is already at its final value. Use let. If you are still writing var in 2026, this is the universe telling you to stop.

// broken for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // prints 3, 3, 3 } // correct for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); // prints 0, 1, 2 }

For deeper context on what these structures actually cost at the hardware level, see the posts on hash map time complexity and array vs linked list performance. If you are deciding whether JavaScript is the right choice for your interview language overall, best language for coding interviews has the full comparison. And for the five gotchas in the broader JavaScript interview environment, JavaScript for Coding Interviews goes into closures, type coercion, and the prototype chain.

Further Reading