JavaScript for Coding Interviews: Built-ins, Gotchas, and What's Missing

- Array.sort() is lexicographic by default -- always pass
(a, b) => a - bfor numbers or your output will silently break. - JavaScript has no built-in heap -- memorize the ~35-line MinHeap before your interview; it comes up in every top-K and Dijkstra problem.
- Map beats Object for dynamic key-value accumulation: any key type, O(1)
.size, and no prototype footgun. Array.shift()is O(n), not O(1) -- use a head pointer for BFS queues to keep traversal at O(V+E).??beats||when 0 or empty string are valid defaults;||silently skips valid zero counts in frequency maps.typeof null === "object"is a historical JS bug -- always guard null explicitly in tree traversal.Number.isNaN()not the globalisNaN()-- the global coerces strings and gives false positives.
You fire up your interview, pick JavaScript, and feel good about it. You write JS every day. How bad could it be? Then [10, 9, 2, 1].sort() returns [1, 10, 2, 9], your BFS quietly becomes O(V²), and typeof null === "object" blows up your tree traversal. The language works fine. You just didn't know where the mines were.
This is the map.
Is JavaScript Worth It for Coding Interviews?
Yes, with conditions.
JavaScript is a reasonable choice if you already write it every day. The syntax is concise, closures make certain patterns elegant, and browser-based interview tools often expect it. You can express most algorithms in fewer lines than Java.
The downsides are real though. JavaScript has no built-in heap, no ordered map, and a sort method that behaves like a trolley problem with no right answer unless you know to pass a comparator. You can work around all of this, but you have to know the workarounds exist before you need them. In an interview, "I didn't know that" and "I forgot" feel identical from the outside.
If you're weighing your options, see Best Language for Coding Interviews.
The Four Data Structures That Cover 90% of Problems
JavaScript gives you four built-in containers worth knowing deeply.
Array
The workhorse. Use it as a stack (push/pop), a dynamic array, or a fixed-size sequence. The operations that burn people are shift() and unshift(): both are O(n) because every element shifts by one index.
const stack = []; stack.push(1); // O(1) stack.pop(); // O(1) stack.unshift(0); // O(n) -- don't use in tight BFS loops stack.shift(); // O(n) -- same warning
For BFS queues, shift() is fine on small inputs. At scale it turns your O(V+E) traversal into something much worse. The fix is a head pointer, covered below.
Map
Map is strictly better than Object for most DSA use cases. It allows any key type (numbers, objects, even arrays), has a .size property instead of requiring Object.keys(m).length, and preserves insertion order.
const freq = new Map(); freq.set('a', (freq.get('a') ?? 0) + 1); freq.has('a'); // O(1) freq.delete('a'); // O(1) freq.size; // property, not a method call
Reach for plain Object when you want terser syntax for static string-keyed lookups. For dynamic accumulation, use Map. See Hash Map Time Complexity for why the O(1) guarantee holds and when it quietly doesn't.
Set
Same API shape as Map but stores only keys. O(1) add, has, and delete. Iteration is insertion-order.
const visited = new Set(); visited.add(node); if (visited.has(node)) return;
One trap: Set equality is reference equality for objects. Two arrays [1, 2] and [1, 2] are different Set entries. If you need to deduplicate arrays by value, serialize them first (JSON.stringify(arr)).
Object
Use Object when your keys are strings and you want the terse literal syntax. The prototype footgun is real: a key of "__proto__" or "constructor" can behave unexpectedly. For safe accumulation, prefer Map or use Object.create(null).
const adj = {}; // adjacency list adj[u] = adj[u] || []; adj[u].push(v);
Decision table:
| Need | Use |
|---|---|
| Dynamic key-value, any key type | Map |
| Fast membership test | Set |
| String-keyed literal / simple config | Object |
| Stack | Array with push/pop |
| Queue (small input) | Array with push/shift |
| Queue (large input) | Array with head pointer |
What JavaScript Is Missing (and How to Fill the Gaps)
No Native Heap
Priority queues appear in Dijkstra's, Prim's, A*, scheduling, and every top-K problem. Java has PriorityQueue. Python has heapq. JavaScript has nothing built in, so you need a ~35-line MinHeap memorized before your interview.
The interviewer is not going to wait while you reinvent it from scratch under pressure. Have this ready to paste:
class MinHeap { constructor(compareFn = (a, b) => a - b) { this.data = []; this.compare = compareFn; } push(val) { this.data.push(val); this._bubbleUp(this.data.length - 1); } pop() { const top = this.data[0]; const last = this.data.pop(); if (this.data.length > 0) { this.data[0] = last; this._sinkDown(0); } return top; } peek() { return this.data[0]; } size() { return this.data.length; } _bubbleUp(i) { while (i > 0) { const parent = (i - 1) >> 1; if (this.compare(this.data[i], this.data[parent]) >= 0) break; [this.data[i], this.data[parent]] = [this.data[parent], this.data[i]]; i = parent; } } _sinkDown(i) { const n = this.data.length; while (true) { let min = i; const l = 2 * i + 1, r = 2 * i + 2; if (l < n && this.compare(this.data[l], this.data[min]) < 0) min = l; if (r < n && this.compare(this.data[r], this.data[min]) < 0) min = r; if (min === i) break; [this.data[i], this.data[min]] = [this.data[min], this.data[i]]; i = min; } } }
For a max-heap, pass (a, b) => b - a. For objects: (a, b) => a.dist - b.dist. See The Heap Data Structure for the theory behind the sift operations.
No Sorted Map
There is no JavaScript equivalent to Java's TreeMap or C++'s std::map. If you need sorted keys with O(log n) lookup, maintain a sorted array and binary-search it, or implement a BST. Most interview problems that call for a sorted map will accept either approach. Most interviewers will accept "in Python I'd use sortedcontainers" as a note and move on.
shift() Performance
Array.shift() is O(n), not O(1). On a large BFS queue this turns O(V+E) into O(V² + E). The fix costs one line:
let head = 0; const queue = [startNode]; while (head < queue.length) { const node = queue[head++]; // O(1) instead of O(n) for (const neighbor of adj[node]) queue.push(neighbor); }
See Dynamic Array Time Complexity for why index-based dequeue beats shifting.
Five Gotchas That Will Burn You
1. Array.sort() Is Lexicographic by Default
[10, 9, 2, 1].sort() returns [1, 10, 2, 9]. Not a bug. JavaScript converts elements to strings and sorts them lexicographically unless you pass a comparator. This is the single most common silent bug in JavaScript interview submissions because it doesn't throw. Your code runs. It just outputs nonsense.
Always pass a comparator when sorting numbers.
arr.sort((a, b) => a - b); // ascending arr.sort((a, b) => b - a); // descending arr.sort((a, b) => a.val - b.val); // objects by field

Technically correct, the best kind of correct.
2. NaN Is Not Equal to Itself
NaN !== NaN is true. result === NaN never works, even when result is NaN. The global isNaN() coerces its argument, so isNaN("hello") returns true. Use Number.isNaN() instead.
Number.isNaN(NaN); // true Number.isNaN("hello"); // false -- no coercion isNaN("hello"); // true -- coerces first, unreliable
3. typeof null === "object"
A historical bug that cannot be fixed without breaking the web. It matters in tree traversal:
// crashes when node is null if (typeof node === "object") processNode(node.val); // correct: guard null explicitly if (node === null) return;
Always guard null explicitly before property access. Optional chaining (node?.val) handles this cleanly and is safe to use in modern Node environments.
4. No Integer Type
JavaScript uses IEEE 754 double-precision float for all numbers. Exact integers are guaranteed only up to 2^53 - 1 (Number.MAX_SAFE_INTEGER = 9007199254740991). For most interview problems this is fine. For problems with large products or hash computations, you can lose precision silently.
2 ** 53 === 2 ** 53 + 1 // true -- precision lost, no error thrown
BigInt exists for when you need it, but it's slower and most LeetCode problems don't require it. Know the limit exists.
5. var vs let in Closures
// Bug: all callbacks share one `i` binding for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // prints: 3, 3, 3 // Fix: let creates a new binding per iteration for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // prints: 0, 1, 2
Use let and const everywhere. var is function-scoped, not block-scoped, and it hoists. There is no situation in interview code where var is the right choice. If you write var during an interview, expect a follow-up.

JavaScript quirks are like buses. You wait your whole life and then five show up at once.
Patterns That Save You Every Time
Use ?? instead of || when 0 or empty string are valid values.
// Bug: skips valid count of 0 const count = map.get(key) || 0; // Correct: only treats null and undefined as missing const count = map.get(key) ?? 0;
For frequency maps, computeIfAbsent doesn't exist in JavaScript. The idiomatic one-liner:
const freq = new Map(); for (const char of s) { freq.set(char, (freq.get(char) ?? 0) + 1); }
Destructuring keeps graph iteration readable:
for (const [node, neighbors] of adj.entries()) { for (const [neighbor, weight] of neighbors) { ... } }
Array.from({length: n}, (_, i) => i) creates a range without a loop, which is useful for initializing union-find parents or building test arrays.
JavaScript Coding Interview Cheat Sheet
| Operation | Syntax | Time |
|---|---|---|
| Array push / pop | arr.push(x) / arr.pop() | O(1) |
| Array shift / unshift | arr.shift() / arr.unshift(x) | O(n) |
| Map get / set / has / delete | m.get(k) / m.set(k,v) | O(1) |
| Map size | m.size (property, not method) | O(1) |
| Set add / has / delete | s.add(v) / s.has(v) / s.delete(v) | O(1) |
| Iterate Map | for (const [k, v] of map) | |
| Iterate Set | for (const v of set) | |
| Sort numbers ascending | arr.sort((a, b) => a - b) | O(n log n) |
| Safe NaN check | Number.isNaN(x) | |
| Null guard | if (node === null) return | |
| Safe default for 0 | map.get(k) ?? 0 | |
| Integer max safe | Number.MAX_SAFE_INTEGER (2^53-1) | |
| Range array | Array.from({length: n}, (_, i) => i) |
Knowing the patterns and not panicking when one appears under pressure are different skills. SpaceComplexity runs voice-based mock interviews with rubric feedback on your code and communication so you can close that gap before it counts.