Array.prototype.sort()

Two traps in one method: it mutates the array you gave it, and without a comparator it sorts numbers as strings. Both are visible in the demo below.

Array methodES1 (1997)Live demo
Common call
items.sort((a, b) => a - b)
Returns
the same array, mutated — not a copy
Replaces
nothing; but toSorted() is the non-mutating version
Watch out
the default comparator converts everything to a STRING
array.sort([compareFn])
→ Array

Demo

Live evaluation
Try:
Inputs
itemsnumber[]numbers, comma separated
Output
[10, 9, 1].sort()
[1, 10, 9]

Look at the first case: [10, 9, 1] sorts to [1, 10, 9], not [1, 9, 10]. Without a comparator every element is converted to a STRING first, and "10" sorts before "9" because the character 1 comes before 9. Now look at the last two number cases — [1, 10, 2] and [100, 25, 3] come back completely UNCHANGED, because they were already in string order. And the single-digit case sorts perfectly, because for one digit string order and numeric order agree. That is why this bug survives testing so reliably: it is invisible on small numbers and silent on the rest. Pass (a, b) => a - b whenever the elements are numbers.

Parameters

NameTypeRequiredDescription
compareFnFunctionno (string comparison)Called as compareFn(a, b). Return a negative number to place a first, positive to place b first, 0 to treat them as equal. Omitted, elements are converted to strings and compared by UTF-16 code unit.

Return value

Array — The SAME array, sorted in place. The return value is a reference to the original, not a copy.

Common patterns

Sort numbers correctly
The comparator every numeric sort needs.
items.sort((a, b) => a - b);
Sort without mutating
Copy first, or use toSorted on modern runtimes.
const sorted = [...items].sort((a, b) => a - b);
const sorted2 = items.toSorted((a, b) => a - b);  // ES2023
Sort objects by a field
localeCompare for strings, subtraction for numbers.
users.sort((a, b) => a.name.localeCompare(b.name));

Examples

1. The classic trap
[10, 9, 1].sort()
Returns
[1, 10, 9]
2. With a comparator
[10, 9, 1].sort((a, b) => a - b)
Returns
[1, 9, 10]
3. Strings are fine
['b', 'a'].sort()
Returns
['a', 'b']
4. It mutates
const a = [3, 1]; a.sort(); a
Returns
[1, 3]
5. Returns the same array
const a = [3, 1]; a.sort() === a
Returns
true
6. Non-mutating
[10, 9, 1].toSorted((a, b) => a - b)
Returns
[1, 9, 10]

Pitfalls

1. The default comparator sorts numbers as strings
The most reported JavaScript surprise there is. Elements are converted to strings and compared character by character, so "10" < "9". Arrays of single digits sort correctly by coincidence, which lets the bug through review.
String order
[10, 9, 1].sort()
[1, 10, 9]
Numeric comparator
[10, 9, 1].sort((a, b) => a - b)
[1, 9, 10]
2. It mutates the array in place
sort does not return a copy — it reorders the original and hands back the same reference. Sorting a prop, a cached array or anything shared changes it for every other holder.
Source reordered
const a = [3, 1];
const b = a.sort();
a
[1, 3] // a changed too
Copy first
const b = [...a].sort();
a is untouched
3. A comparator returning a boolean is broken
sort needs a NUMBER. A boolean coerces to 1 or 0 and never to a negative, so the comparator can never say "a comes first" — the result is subtly and inconsistently wrong rather than reversed.
Boolean returned
[3, 1, 2].sort((a, b) => a > b)
[3, 1, 2] // V8 leaves it untouched
Return a number
[3, 1, 2].sort((a, b) => a - b)
[1, 2, 3]
4. a - b breaks on strings
Subtraction on non-numeric strings gives NaN, and a NaN comparator result is treated as 0 — so the array comes back in whatever order it started. Use localeCompare for text.
NaN comparator
['b', 'a'].sort((a, b) => a - b)
['b', 'a'] // unchanged
localeCompare
['b', 'a'].sort((a, b) => a.localeCompare(b))
['a', 'b']

When to use

Use it
  • Ordering an array you own and are happy to mutate
  • Any sort where you can supply an explicit comparator
  • Sorting objects by a field with localeCompare or subtraction
Reach for something else
  • The array is shared or a prop → copy first, or use toSorted
  • You have not written a comparator and the elements are numbers
  • You need a stable numeric sort of mixed types → normalise first

Notes

Complexity
O(n log n); stable since ES2019, so equal elements keep their relative order
Return
The same array object, reordered — not a copy
CPython impl
V8: TimSort, in third_party/v8/builtins/array-sort.tq
Memory
In place, aside from TimSort's temporary merge buffer
Thread-safe
Single-threaded; a comparator that mutates the array gives undefined behaviour

FAQ

Because the default comparator converts every element to a string and compares UTF-16 code units. "10" begins with the character 1, which sorts before 9, so 10 lands before 9. Pass (a, b) => a - b for numbers.

[10, 9, 1].sort((a, b) => a - b)
// [1, 9, 10]

History

ES1
sort has been present since the first standard in 1997, string comparator and all.
ES2019
Stability became a requirement rather than an engine detail.
ES2023
toSorted added — the non-mutating counterpart, alongside toReversed and toSpliced.