Common Algorithm Pitfalls

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.

5 min read Level 3/5 #dsa#interview#pitfalls
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 →