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

May 26, 20269 min read
dsaalgorithmsinterview-prepleetcode
JavaScript for Coding Interviews: Built-ins, Gotchas, and What's Missing
TL;DR
  • Array.sort() is lexicographic by default -- always pass (a, b) => a - b for 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 global isNaN() -- 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:

NeedUse
Dynamic key-value, any key typeMap
Fast membership testSet
String-keyed literal / simple configObject
StackArray 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

JavaScript sort behavior being wrong but technically correct per spec

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.

Yet another JavaScript quirk showing unexpected closure behavior

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

OperationSyntaxTime
Array push / poparr.push(x) / arr.pop()O(1)
Array shift / unshiftarr.shift() / arr.unshift(x)O(n)
Map get / set / has / deletem.get(k) / m.set(k,v)O(1)
Map sizem.size (property, not method)O(1)
Set add / has / deletes.add(v) / s.has(v) / s.delete(v)O(1)
Iterate Mapfor (const [k, v] of map)
Iterate Setfor (const v of set)
Sort numbers ascendingarr.sort((a, b) => a - b)O(n log n)
Safe NaN checkNumber.isNaN(x)
Null guardif (node === null) return
Safe default for 0map.get(k) ?? 0
Integer max safeNumber.MAX_SAFE_INTEGER (2^53-1)
Range arrayArray.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.


Further Reading