Free lesson · DSA Mastery - Interview Prep, End to End
JavaScript for DSA
JavaScript for DSA
JavaScript is an acceptable language for technical interviews, particularly for frontend roles or at companies where the team works heavily with web technologies. It is more concise than Java and approaches Python's expressiveness, but its built-in data structures have some sharp edges that bite engineers who do not know them.
This topic covers what you need to know to use JavaScript effectively for DSA problems. The focus is on the gaps where JavaScript lacks built-in support (priority queue, sorted set) and the idioms that make up for it.
Why JavaScript for Interviews
Interactive animation: What JavaScript gives you in an interview, and what it gives up.
JavaScript has real advantages:
- Concise syntax. Arrow functions, destructuring, and rest/spread operators reduce boilerplate.
- Dynamic typing. Like Python, less time satisfying the compiler.
- First-class functions. Higher-order functions and callbacks are idiomatic.
- Familiar to web companies. If your target role is frontend or fullstack, JavaScript may be the natural choice.
The major trade-off: JavaScript lacks built-in support for several data structures interview problems need. No priority queue. No sorted set. No ordered map. You either implement these yourself or work around their absence. This is a significant downside compared to Java, C++, Python, or C#.
For backend-heavy or systems interviews, JavaScript is not the best choice. For frontend interviews, it is reasonable and often expected.
The Core Data Structures
Interactive animation: JavaScript's toolbox - and the priority queue that is not built in.
JavaScript's built-in collections:
| Need | JavaScript Type | Notes |
|---|---|---|
| Array | Array |
Dynamic, mixed types |
| Hash map | Map or plain object |
Map is preferred for non-string keys |
| Hash set | Set |
O(1) average |
| Stack | Array (use push/pop) |
Fast at the end |
| Queue | Array (slow) or custom |
shift() is O(n) |
| Deque | Custom | No built-in |
| Priority queue | Custom | No built-in |
| Sorted set / map | Custom | No built-in |
Arrays
JavaScript arrays are dynamic and can hold mixed types.
const arr = [5, 2, 8, 1, 9, 3];
arr.push(10); // add to end
arr.pop(); // remove and return last
arr.unshift(0); // add to front (O(n))
arr.shift(); // remove from front (O(n))
arr[0]; // index
arr[arr.length - 1]; // last element
arr.length; // size
arr.includes(5); // membership check (O(n))
arr.indexOf(5); // first index (O(n))
arr.slice(1, 4); // slice (new array)
arr.splice(1, 2); // remove 2 elements starting at index 1
Note that shift() and unshift() are O(n) because they re-index the entire array. This makes plain arrays a poor choice for queues. More on this below.
Iteration patterns:
for (const x of arr) {
// x is the value
}
for (let i = 0; i < arr.length; i++) {
// index access
}
arr.forEach((val, i) => {
// val is value, i is index
});
Functional patterns are widely used in JavaScript:
arr.map(x => x * 2); // transform
arr.filter(x => x > 0); // keep matching
arr.reduce((sum, x) => sum + x, 0); // accumulate
arr.find(x => x > 5); // first match
arr.findIndex(x => x > 5); // index of first match
arr.every(x => x > 0); // all match?
arr.some(x => x > 0); // any match?
These are clean and idiomatic. For interview code where performance matters, raw for loops are slightly faster but rarely make a difference.
For 2D arrays:
const grid = Array.from({ length: 3 }, () => Array(4).fill(0));
// 3x4 grid initialized to 0
Use Array.from with a factory function for the same reason you avoid [[0] * n] * m in Python: shared references.
Map and Object
Interactive animation: When to use a Map and when a plain object is enough.
JavaScript has two ways to store key-value pairs: plain objects and the Map class.
// Plain object
const counts = {};
counts["a"] = 1;
counts["a"] = (counts["a"] || 0) + 1;
counts["b"] = 5;
if (counts["a"] !== undefined) {
const value = counts["a"];
}
if ("a" in counts) { } // also works
Plain objects work fine for string keys but have downsides:
- Keys are always strings (or coerced to strings).
obj[5]andobj["5"]are the same key. - There is no clean way to count the number of keys without
Object.keys(obj).length. - Prototype pollution: keys like
"constructor"or"__proto__"can cause weird bugs.
For interview code, Map is generally cleaner:
const counts = new Map();
counts.set("a", 1);
counts.set("a", (counts.get("a") || 0) + 1);
counts.set("b", 5);
if (counts.has("a")) {
const value = counts.get("a");
}
counts.size; // size of the map
counts.delete("a");
Map allows any type as a key (including objects), preserves insertion order, and has a clean API. Use Map unless you specifically need a plain object.
Set
Set is JavaScript's hash set.
const seen = new Set();
seen.add(5);
if (seen.has(5)) { }
seen.delete(5);
seen.size;
Iteration:
for (const x of seen) {
// ...
}
// Convert to array:
const arr = [...seen];
const arr2 = Array.from(seen);
The spread operator [...seen] is widely used for converting sets and maps to arrays.
Stack and Queue
Interactive animation: shift() re-indexes the whole array; a head index makes dequeue O(1).
JavaScript arrays work fine as stacks. push and pop are O(1).
const stack = [];
stack.push(1);
stack.push(2);
const top = stack.pop(); // 2
stack[stack.length - 1]; // peek
For queues, the situation is annoying. array.shift() is O(n) because it re-indexes. For small queues this does not matter. For large queues (BFS on 100,000 nodes, for example), it becomes a bottleneck.
A simple workaround is using an index instead of shifting:
const queue = [];
let head = 0;
queue.push(1);
queue.push(2);
queue.push(3);
while (head < queue.length) {
const front = queue[head++];
// process front
}
This trades memory for time - old elements stay in the array but are skipped via the index. For interview problems, this is usually fine.
For a real deque (operations on both ends), you have to either implement a doubly-linked list or use the index trick on both ends. JavaScript's lack of a built-in deque is a real gap.
Priority Queue (Implementing Your Own)
Interactive animation: No built-in priority queue: three ways around the gap.
JavaScript has no built-in priority queue. For interview problems that need one, you have a few choices:
- Implement a heap from scratch. Tedious but doable in 30-40 lines.
- Use an external library. Some platforms allow
@datastructures-js/priority-queueor similar. - Use a sorted array as a substitute. Insertions are O(n), but for small heaps this is acceptable.
A minimal min-heap implementation:
class MinHeap {
constructor() {
this.heap = [];
}
push(val) {
this.heap.push(val);
this._bubbleUp(this.heap.length - 1);
}
pop() {
const top = this.heap[0];
const last = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = last;
this._bubbleDown(0);
}
return top;
}
peek() { return this.heap[0]; }
size() { return this.heap.length; }
_bubbleUp(i) {
while (i > 0) {
const parent = (i - 1) >> 1;
if (this.heap[i] < this.heap[parent]) {
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
i = parent;
} else break;
}
}
_bubbleDown(i) {
const n = this.heap.length;
while (true) {
const left = 2 * i + 1, right = 2 * i + 2;
let smallest = i;
if (left < n && this.heap[left] < this.heap[smallest]) smallest = left;
if (right < n && this.heap[right] < this.heap[smallest]) smallest = right;
if (smallest === i) break;
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
i = smallest;
}
}
}
This is a real downside of JavaScript for interviews. If you have any reason to anticipate priority-queue problems, memorize this implementation or have it ready.
Interactive animation: The lesson MinHeap, part one: push and bubble up, line by line.
Interactive animation: Push 1 into the heap [2, 5, 8], swap by swap.
Interactive animation: Part two: pop and bubble down, line by line.
Strings
JavaScript strings are immutable.
const s = "hello";
s.length; // 5
s[0]; // 'h'
s.toUpperCase(); // "HELLO"
s.toLowerCase(); // "hello"
s.substring(1, 4); // "ell" (start, end exclusive)
s.slice(1, 4); // same
s.indexOf("ll"); // 2
s.includes("ll"); // true
s.replace("l", "L"); // "heLlo" (only first)
s.replaceAll("l", "L"); // "heLLo"
s.split(","); // ["hello"]
[...s]; // ['h', 'e', 'l', 'l', 'o']
s.split("").reverse().join(""); // reverse a string
For repeated concatenation, prefer joining at the end rather than += in a loop:
// Avoid:
let result = "";
for (const part of parts) result += part;
// Prefer:
const result = parts.join("");
Modern JavaScript engines optimize the first pattern in some cases, but the second is always safe.
Sorting
Interactive animation: sort() compares as strings unless you pass a comparator: 10 sorts before 2.
JavaScript's Array.prototype.sort is in place. Without a comparator, it sorts as strings - including numbers, which is usually not what you want.
const arr = [3, 1, 4, 1, 5, 9, 2, 6];
arr.sort(); // BUG: sorts as strings -> [1, 1, 2, 3, 4, 5, 6, 9] (looks right for single digits)
arr.sort((a, b) => a - b); // ascending numeric
arr.sort((a, b) => b - a); // descending numeric
Always pass a comparator. The default behavior surprises people.
For sorting by attribute:
intervals.sort((a, b) => a[0] - b[0]); // sort 2D by first element
words.sort((a, b) => a.length - b.length); // sort by length
Common Idioms
Interactive animation: Destructuring, swap, counting with a Map and building grids.
A few JavaScript patterns that show up frequently.
Destructuring
const [a, b] = [1, 2];
const [first, ...rest] = [1, 2, 3, 4];
const { name, age } = person;
Useful for clean swap, multi-return, and destructuring arguments.
Swap
[a, b] = [b, a];
JavaScript's destructuring makes swap a one-liner without a temp variable.
Counting with Map
const counts = new Map();
for (const c of s) {
counts.set(c, (counts.get(c) || 0) + 1);
}
Building an Adjacency List
const adj = new Map();
for (const [u, v] of edges) {
if (!adj.has(u)) adj.set(u, []);
if (!adj.has(v)) adj.set(v, []);
adj.get(u).push(v);
adj.get(v).push(u);
}
BFS Template
Interactive animation: The BFS template line by line: an adjacency Map and a head-index queue.
const queue = [start];
let head = 0;
const visited = new Set([start]);
while (head < queue.length) {
const node = queue[head++];
for (const next of adj.get(node) || []) {
if (!visited.has(next)) {
visited.add(next);
queue.push(next);
}
}
}
Tree Node and List Node Classes
Standard interview classes:
class TreeNode {
constructor(val, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
Common Mistakes to Avoid
Interactive animation: Six traps, and the habit that avoids each one.
array.shift() is O(n). Use the index trick or a real deque implementation for large queues.
Array.sort() without a comparator sorts as strings. Always pass (a, b) => a - b for numeric sorts.
== vs ===. == does type coercion (5 == "5" is true). Always use === for strict equality.
Number precision. JavaScript numbers are 64-bit floats. Integer arithmetic is exact only up to 2^53. For very large integers, use BigInt:
const big = 9007199254740993n;
Hash map key types. Plain objects coerce keys to strings. Map preserves the type.
for...in vs for...of. for...in iterates over keys (including inherited ones - usually not what you want). for...of iterates over values. Use for...of for arrays.
Boilerplate to Remember
A minimal JavaScript interview template:
class Solution {
solve(nums) {
const counts = new Map();
for (const x of nums) {
counts.set(x, (counts.get(x) || 0) + 1);
}
return 0;
}
}
That covers most of what you need. JavaScript's expressiveness compensates for some of the awkwardness around missing data structures.
If you anticipate heap or priority-queue problems, memorize the MinHeap implementation above before the interview. It is the single most common piece of "you have to implement this yourself" code in JavaScript interview prep.
The rest of this course uses Java for code examples. JavaScript ports of the algorithms are usually shorter and cleaner - translate by replacing type declarations with const/let and using map/filter/reduce where appropriate.