JavaScript Coding Interview: Array Method Pitfalls to Avoid
JavaScript's array methods make interview code short and readable. They also hide some of the most common bugs in live rounds. Numbers sort as strings, map(parseInt) returns NaN, a 2D grid turns out to be one shared row, and a return inside forEach does nothing you expected.
This guide covers the JavaScript coding interview array method pitfalls that come up most, why each one happens, and the fix you can type without thinking. All examples run in a modern Node.js or browser console.
Pitfall 1: sort() compares strings by default
[10, 9, 1, 100].sort(); // [1, 10, 100, 9]
[10, 9, 1, 100].sort((a, b) => a - b); // [1, 9, 10, 100]
Without a comparator, sort converts elements to strings and compares them by UTF-16 code units. For numbers, always pass (a, b) => a - b (ascending) or (a, b) => b - a (descending).
For sorting pairs or intervals:
intervals.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
The || breaks ties on the second element, because 0 is falsy.
Pitfall 2: sort() and reverse() mutate in place
const words = ['b', 'a', 'c'];
const sorted = words.sort();
sorted === words; // true: the original array changed
If the caller's array shouldn't change, copy it first, [...arr].sort(...), or use toSorted / toReversed where your runtime supports them (ES2023). Say it out loud: "I'll copy so I don't mutate the input."
Pitfall 3: map(parseInt)
['1', '2', '3'].map(parseInt); // [1, NaN, NaN]
map passes (value, index, array), and parseInt takes (string, radix). Index 1 becomes radix 1, which is invalid, and '3' in radix 2 is NaN. Fix:
['1', '2', '3'].map(Number); // [1, 2, 3]
['1', '2', '3'].map(s => parseInt(s, 10));
Pitfall 4: Array(n).fill([]) shares one array
const grid = Array(3).fill([]);
grid[0].push(1);
// grid is [[1], [1], [1]]: every row is the same object
fill puts the same reference in every slot. Build rows independently:
const grid = Array.from({ length: m }, () => Array(n).fill(0));
const buckets = Array.from({ length: k }, () => []);
fill(0) is safe because numbers are primitives. The problem only comes up with objects and arrays.
Pitfall 5: return inside forEach doesn't return
function hasTwo(arr) {
arr.forEach(x => {
if (x === 2) return true; // returns from the callback only
});
return false;
}
hasTwo([1, 2, 3]); // false
forEach can't be stopped early, except by throwing. Use the method that matches what you mean:
| You want | Use |
|---|---|
| Does any element match? | some |
| Do all elements match? | every |
| First matching element | find (returns undefined if none) |
| Index of first match | findIndex (returns -1 if none) |
| Early exit with complex logic | a plain for...of loop with break / return |
In interviews, a plain for loop is often the clearest choice anyway.
Pitfall 6: reduce on an empty array
[].reduce((a, b) => a + b); // TypeError: Reduce of empty array with no initial value
[].reduce((a, b) => a + b, 0); // 0
Always pass an initial value. It also makes the accumulator's type obvious: 0 for sums, {} or new Map() for counts, [] for building lists.
Pitfall 7: shift() in a BFS queue
while (queue.length) {
const node = queue.shift(); // O(n) per call in typical engines
// ...
}
shift re-indexes the array, so a large BFS can become quadratic. For interview-sized inputs it's usually fine, but it's worth knowing the alternative:
const queue = [start];
let head = 0;
while (head < queue.length) {
const node = queue[head++];
for (const next of neighbors(node)) {
if (!seen.has(next)) {
seen.add(next);
queue.push(next);
}
}
}
Say it: "I'm using a head pointer instead of shift so dequeue stays O(1)."
Pitfall 8: indexOf vs includes with NaN, and strict equality
[NaN].indexOf(NaN); // -1
[NaN].includes(NaN); // true
[1, 2, 3].includes('2'); // false (strict comparison, no coercion)
Inputs parsed from strings can surprise you here. Normalize types before you search.
Pitfall 9: slice vs splice
const a = [1, 2, 3, 4, 5];
a.slice(1, 3); // [2, 3]; a unchanged (end index exclusive)
a.splice(1, 2); // [2, 3]; a is now [1, 4, 5] (start, deleteCount)
slice(start, end) copies. splice(start, count, ...items) mutates. Mixing them up is a classic off-by-something bug.
Pitfall 10: shallow copies of 2D arrays
const copy = [...grid];
copy[0][0] = 9; // also changes grid[0][0]
Spread copies the outer array only. For a grid of primitives:
const copy = grid.map(row => [...row]);
structuredClone(grid) works too in modern runtimes, but the map version is clear enough and easy to explain.
Pitfall 11: Math.max on empty or huge arrays
Math.max(); // -Infinity
Math.max(...[]); // -Infinity
Check for empty input explicitly. Spreading a very large array into Math.max can also exceed the engine's argument limit and throw a RangeError. A loop or reduce with an initial value avoids that.
A few safe idioms
const last = arr.at(-1); // last element
const counts = new Map();
for (const x of arr) counts.set(x, (counts.get(x) ?? 0) + 1);
const unique = [...new Set(arr)];
const flat = nested.flat(); // one level
const pairs = Object.entries(obj); // [key, value][]
Prefer Map and Set over plain objects for counting and membership. Keys keep their type (the number 1 stays a number), and there's no prototype to worry about.
Talk track while coding
"I'm passing a numeric comparator because the default sort is lexicographic."
"I'll build the grid with
Array.fromso each row is its own array."
"I'm using
someinstead offorEachbecause I want to stop at the first match."
These short sentences show the interviewer you know the language, and they keep you from going quiet.
Soft practice note
These pitfalls rarely show up when you're relaxed. They show up when the clock is running and someone is watching. If you want timed, out-loud practice to make the safe idioms automatic, Luna Interview is built for calm, structured interview rehearsal and prep before the real round.
Pre-interview checklist
- [ ] Numeric comparator on every number sort
- [ ] Copy before sorting if the input shouldn't change
- [ ]
map(Number), nevermap(parseInt) - [ ]
Array.from({ length }, () => ...)for 2D arrays - [ ]
some/every/find/for...ofinstead of returning fromforEach - [ ] Initial value on every
reduce - [ ] Head pointer for big BFS queues
- [ ]
slicecopies,splicemutates - [ ] Deep copy rows with
map(row => [...row])
Learn the handful of traps once, and JavaScript's array methods go back to saving you time.
Rehearse with an AI interview copilot, then browse commonly reported interview questions.