The Bugs That Catch Experienced Engineers Off-Guard in Interviews
Common Algorithm Pitfalls
Off-by-one errors, mutating arrays while iterating, accidental O(n²) from Array.shift, JavaScript's lexical default sort, and reference vs copy semantics on Maps — the five pitfalls most likely to break a correct algorithm at the last moment.
What you'll learn
- Fix an off-by-one error in a binary search loop boundary
- Explain why Array.shift in a loop is O(n²) and name the O(1) alternative
- Show the correct way to clone a Map entry value to avoid shared-reference bugs
An algorithm can be perfectly designed and still wrong in practice because of a small implementation detail. These five pitfalls appear repeatedly in code reviews, bugs reports, and — at the worst moment — in live interview sessions.
1. Off-by-One in Loop Boundaries
Binary search is the canonical example. The boundary choice (< vs <=,
mid + 1 vs mid) must be consistent with your invariant.
// Bug: mid is never searched when left === right → misses last element
function searchBuggy(nums, target) {
let left = 0, right = nums.length - 1;
while (left < right) { // should be left <= right
const mid = (left + right) >> 1;
if (nums[mid] === target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// Fix: use left <= right so a single-element window is still checked
function searchCorrect(nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
const mid = (left + right) >> 1;
if (nums[mid] === target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
} 2. Mutating an Array While Iterating It
Deleting or inserting elements mid-loop skips or double-processes items because indices shift under you.
const nums = [1, 2, 3, 4, 5];
// Bug: splice shifts elements left; index 2 is skipped after removing index 1
for (let i = 0; i < nums.length; i++) {
if (nums[i] % 2 === 0) nums.splice(i, 1); // mutating while iterating
}
console.log(nums); // [1, 3, 5] — looks right but only by coincidence here
// Fix: iterate backwards, or filter into a new array
const evensRemoved = nums.filter((n) => n % 2 !== 0); 3. Accidental O(n²) from Array.shift
Array.shift removes index 0 and shifts every remaining element left —
O(n) per call. A loop that calls it n times is O(n²).
// O(n²) — common mistake when implementing BFS or a queue
while (queue.length) {
const node = queue.shift(); // O(n) each time!
// ...
}
// O(n) fix: use an index pointer instead of shifting
let head = 0;
while (head < queue.length) {
const node = queue[head++]; // O(1)
// ...
} 4. JavaScript’s Default Sort Is Lexical
Array.prototype.sort converts elements to strings by default. Sorting numbers
without a comparator produces wrong results silently.
const nums = [10, 9, 2, 21, 3];
console.log(nums.sort()); // [10, 2, 21, 3, 9] — lexical!
console.log(nums.sort((a, b) => a - b)); // [2, 3, 9, 10, 21] — numeric Always pass a comparator when sorting numbers.
5. Reference vs Copy on Map Values
Storing an object into a Map stores a reference. Mutating the object later mutates the stored value — which is usually not what you want in a memoisation cache or frequency table.
const memo = new Map();
const state = { count: 0, items: [] };
memo.set('a', state); // stores reference, not a copy
state.count = 99; // mutates the object held in the Map too
console.log(memo.get('a').count); // 99 — surprise!
// Fix: store a shallow or deep copy depending on your needs
memo.set('a', { ...state }); // shallow copy
// or for nested structures:
memo.set('a', JSON.parse(JSON.stringify(state))); // deep copy Up Next
You have covered the full DSA track. The final lesson points you toward resources and next steps to keep growing.
Going Further →