Chapter 6.2☕ 18 min read

Array Mutation Methods: sort() Lies!

10 comes before 2? Yes, in string comparison! And shift() is the slowest array method — know why or get trapped in interviews.

01Push/Pop vs Shift/Unshift: O(1) vs O(n)

push/pop add and remove from the END of an array. They are O(1) amortized — the fastest array operations you can get.

unshift/shift add and remove from the START. They are O(n) because every element must be re-indexed when the first slot changes.

V8 Internal: When you call shift(), V8 moves every element one index position down in memory. Element at index 1 goes to 0, index 2 goes to 1, and so on. For an array of 100,000 elements, that is 100,000 memory writes! push() just writes to the next available slot — one operation. Occasionally V8 must allocate a larger contiguous block and copy everything over, but averaged across many pushes, it works out to amortized O(1).
const arr = [1, 2, 3];

// O(1) - Fast
arr.push(4);    // [1, 2, 3, 4]
arr.pop();      // [1, 2, 3]

// O(n) - Slow (re-indexes everything)
arr.unshift(0); // [0, 1, 2, 3] - shifts 1,2,3 right
arr.shift();    // [1, 2, 3] - shifts 1,2,3 left

// Performance test
const large = new Array(100000).fill(0);

console.time('push');
large.push(1);
console.timeEnd('push'); // < 0.1ms

console.time('unshift');
large.unshift(1);
console.timeEnd('unshift'); // > 1ms (1000x slower!)
02sort() Lies: The String Coercion Trap

sort() WITHOUT a comparator converts elements to STRINGS, then sorts lexicographically. This is the #1 JavaScript sorting bug.

[10, 2, 1].sort() gives [1, 10, 2] because "10" comes before "2" in string order.

// THE LIE - Sort without comparator
const numbers = [10, 2, 30, 1];
numbers.sort();
console.log(numbers); // [1, 10, 2, 30] — WRONG!
// '10' < '2' because '1' comes before '2' in Unicode

// THE FIX - Numeric comparator
const correct = [10, 2, 30, 1];
correct.sort((a, b) => a - b); // Ascending
console.log(correct); // [1, 2, 10, 30] ✅

// Descending
correct.sort((a, b) => b - a);

// String sorting with localeCompare (for non-ASCII)
const words = ['réservé', 'premier', 'Cliché', 'communiqué'];
words.sort((a, b) => a.localeCompare(b));

// ES2023 toSorted — immutable sort
const original = [3, 1, 2];
const sorted = original.toSorted((a, b) => a - b);
console.log(original); // [3, 1, 2] — unchanged ✅
console.log(sorted);   // [1, 2, 3]
V8 uses TimSort for Array.prototype.sort — a hybrid of Merge Sort and Insertion Sort. It's stable (equal elements keep their original order) and O(n log n). But the string coercion step before sorting numbers produces incorrect numeric results. Always provide the comparator!
03splice(): The Array Surgeon

splice(start, deleteCount, ...items) is the Swiss army knife for array mutation. Can delete, insert, and replace simultaneously.

Returns an array of the deleted elements. Negative start index counts from the end. O(n) operation.

const arr = ['a', 'b', 'c', 'd', 'e'];

// Delete 2 elements starting at index 1
const deleted = arr.splice(1, 2);
console.log(arr);     // ['a', 'd', 'e']
console.log(deleted); // ['b', 'c']

// Insert without deleting (deleteCount = 0)
arr.splice(1, 0, 'x', 'y');
console.log(arr); // ['a', 'x', 'y', 'd', 'e']

// Replace (delete and insert in one step)
arr.splice(1, 2, 'B', 'C');
console.log(arr); // ['a', 'B', 'C', 'd', 'e']

// ES2023 toSpliced — immutable version
const original = [1, 2, 3];
const spliced = original.toSpliced(1, 1, 20);
console.log(original); // [1, 2, 3] — unchanged
console.log(spliced);  // [1, 20, 3]
04reverse(), fill() & copyWithin()

reverse() mutates array in place. O(n). ES2023: toReversed().

fill(value, start, end) fills range with a value. WARNING: fill({}) fills with the SAME object reference!

copyWithin(target, start, end) copies a sequence within the array. Fast memory move.

// reverse — mutates!
const arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]

// ES2023 toReversed
const orig = [1, 2, 3];
const rev = orig.toReversed();

// fill — useful for initialization
const grid = new Array(5).fill(0); // [0, 0, 0, 0, 0]

// THE TRAP: fill with objects
const objects = new Array(3).fill({ x: 0 });
objects[0].x = 1;
console.log(objects); // [{ x: 1 }, { x: 1 }, { x: 1 }] — SAME OBJECT!

// Correct way to fill with unique objects
const unique = Array.from({ length: 3 }, () => ({ x: 0 }));

// copyWithin — fast internal copy
const buf = [1, 2, 3, 4, 5];
buf.copyWithin(0, 3, 5); // Copy indices 3-5 to index 0
console.log(buf); // [4, 5, 3, 4, 5]
05ES2023 Immutable Array Methods

ES2023 introduces toSorted(), toReversed(), toSpliced(), and with(): Non-mutating alternatives.

with(index, value) replaces an element at an index and returns a new array (replaces arr[i] = val).

const original = [3, 1, 2];

// Old mutating way
const mutated = [...original];
mutated.sort((a, b) => a - b);

// New immutable way
const sorted = original.toSorted((a, b) => a - b);
console.log(sorted); // [1, 2, 3]

// with() — immutable index update
// Old way: arr[1] = 20 (mutates)
// New way:
const updated = original.with(1, 20); // [3, 20, 2]
console.log(original); // [3, 1, 2] — unchanged!
📋 Pro Tip:
If you are using React, Vue, or any framework that relies on immutability for change detection, the ES2023 methods are a lifesaver. They eliminate the need for awkward spread-copy-then-mutate patterns like [...arr].sort().

Lo kar liya — Key Points:

  • ✅ push/pop are O(1) amortized; shift/unshift are O(n) because they must re-index all elements
  • ✅ sort() without a comparator converts elements to strings — [10, 2].sort() gives [10, 2] because "10" < "2"
  • ✅ Always use sort((a, b) => a - b) for numeric sorting and localeCompare for strings
  • ✅ splice() is the Swiss army knife for deleting, inserting, and replacing elements in an array
  • ✅ fill() with objects shares the same reference — use Array.from with a mapper for unique objects
  • ✅ ES2023 introduces toSorted, toReversed, toSpliced, and with for immutable array operations
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase