DEV Community

ValPetal Tech Labs
ValPetal Tech Labs

Posted on

Javascript Question of the Day #30 [Talk::Overflow]

Why Array.prototype.sort mutates in place and returns the same reference — the silent aliasing bug that corrupts your "original" array after sorting in production JavaScript.

This post explains a quiz originally shared as a LinkedIn poll.


🔹 The Question

const scores = [85, 92, 78, 95, 88];
const ranked = scores.sort((a, b) => b - a);

scores.push(70);

console.log(ranked.length);
console.log(ranked === scores);
console.log(ranked[0]);
Enter fullscreen mode Exit fullscreen mode

Hint: sort() is one of the few array methods that does not return a new array. Think carefully about what ranked actually is before the push happens.

Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/


🔹 Solution

Correct answer: A) 6, true, 95

The output is:

6
true
95
Enter fullscreen mode Exit fullscreen mode

🧠 How this works

This quiz exposes one of the most common and silent bugs in JavaScript array handling: Array.prototype.sort sorts the array in place and returns a reference to the same array — not a new one. This behavior is fundamentally different from the functional array methods developers use daily (map, filter, slice, concat, flat), all of which return new arrays and leave the original untouched.

When scores.sort(...) is called, two things happen simultaneously:

  1. The original scores array is mutated — its elements are reordered in descending order.
  2. The return value is the exact same array object that was sorted — not a copy.

Assigning the return value to ranked does not produce a second array. ranked is just another name for scores. They are the same object in memory. Any mutation applied to one is immediately visible through the other — including the scores.push(70) that comes next.

This is why ranked.length is 6 instead of 5, ranked === scores is true (strict equality checks object identity), and ranked[0] is 95 (the highest score after the descending sort).

🔍 Line-by-line explanation

  1. const scores = [85, 92, 78, 95, 88] — Creates an array of five numbers. Let's call this Object A.

  2. const ranked = scores.sort((a, b) => b - a) — Sorts Object A in place in descending order: [95, 92, 88, 85, 78]. sort() returns a reference to Object A. ranked now points to the same Object A. There is still only one array in memory. Both scores and ranked are aliases for it.

  3. scores.push(70) — Appends 70 to Object A. Since ranked points to the same Object A, ranked is now [95, 92, 88, 85, 78, 70] and its length is 6.

  4. console.log(ranked.length)ranked is Object A, which now has 6 elements. Prints 6.

  5. console.log(ranked === scores)=== on objects checks reference identity. ranked and scores point to the same object. Prints true.

  6. console.log(ranked[0]) — The first element of the descending-sorted array is 95. Prints 95.

The non-obvious part: The developer's mental model is reasonable — in most modern JavaScript, array transformation methods return new arrays. map, filter, reduce, slice, concat, flat, flatMap — all of these give you a new array and preserve the original. sort (and reverse and splice) are the legacy exceptions that mutate in place. The return value of sort() looks like a "sorted copy" in assignment syntax (const ranked = scores.sort(...)), but it's actually the sorted original handed back.

Since ES2023, Array.prototype.toSorted() provides a non-mutating alternative that genuinely returns a new sorted array. The asymmetry between sort and toSorted exists precisely because the original sort behavior is a well-known footgun.


🔹 Real-World Impact

Where this appears:

  • Table sorting in dashboards: A developer fetches a list of records, stores it as originalData, and sorts a "view" copy for display: const sorted = originalData.sort(compareFn). When the user resets filters, originalData is already mutated and sorted — the "original" order is gone. The reset button does nothing meaningful.

  • Leaderboard / ranking features: A game or analytics app maintains a live scores array. A function computes const topFive = scores.sort((a, b) => b - a).slice(0, 5). The .sort() mutates the live scores array, causing all downstream consumers of scores to see it in sorted order rather than insertion order (e.g., recent-activity feeds, historical charts).

  • Pagination logic: An app applies .sort() to paginate data on the frontend. The allItems array used by the page-count calculation is now in sorted order, which may not matter for counts — but if another component iterates allItems expecting insertion order (e.g., to highlight "newly added" items), it silently gets the wrong order.

  • Test isolation failures: A test creates a shared fixture array, sorts it in one test, then expects it to be in original order in a later test. Because sort mutated in place, subsequent tests fail inconsistently depending on execution order — a classic shared-mutable-state test pollution scenario.

🔹 Key Takeaways

  1. Array.prototype.sort mutates the original array and returns the same reference. Unlike map, filter, slice, and concat, sort does not create a new array. Assigning the return value to a new variable creates an alias, not a copy.

  2. Always copy before sorting if you need the original order preserved. Use [...arr].sort(compareFn), arr.slice().sort(compareFn), or Array.from(arr).sort(compareFn) to sort a copy. This is an extremely common defensive pattern in production code.

  3. Array.prototype.toSorted() (ES2023) is the non-mutating alternative. arr.toSorted(compareFn) returns a new sorted array and leaves arr untouched. Prefer it in new code when targeting modern environments. Similarly, toReversed() and toSpliced() are the non-mutating versions of reverse and splice.

  4. Array.prototype.reverse has the same mutation behavior. It reorders elements in place and returns the same array. const reversed = arr.reverse() is an alias, not a copy. arr.toReversed() is the safe alternative.

  5. Strict equality (===) on objects checks identity, not structure. ranked === scores being true is the diagnostic tell. When two variables point to the same object, mutations through either variable affect both. If you suspect aliasing, === confirms it; use it in debugging before reaching for deep-equality utilities.

Top comments (0)