JavaScript sorting and the default sort pitfall

Learn why [10, 9, 1].sort() is wrong, sort with comparators, and write a merge sort.

JavaScript
console.log([10, 9, 1].sort().join(","));
console.log([10, 9, 1].sort((a, b) => a - b).join(","));

function mergeSort(items) {
  if (items.length <= 1) {
    return items;
  }
  const middle = Math.floor(items.length / 2);
  const left = mergeSort(items.slice(0, middle));
  const right = mergeSort(items.slice(middle));
  const merged = [];
  let i = 0;
  let j = 0;
  while (i < left.length && j < right.length) {
    merged.push(left[i] <= right[j] ? left[i++] : right[j++]);
  }
  return merged.concat(left.slice(i), right.slice(j));
}
console.log(mergeSort([5, 2, 9, 1, 5, 6]).join(","));

const people = [
  { name: "Ada", age: 30 },
  { name: "Bo", age: 25 },
  { name: "Cy", age: 30 },
  { name: "Di", age: 25 },
];
people.sort((p, q) => p.age - q.age);
console.log(people.map((p) => p.name).join(","));

console.log(["pear", "Apple", "fig"].sort((a, b) => a.localeCompare(b)).join(","));
Output
1,10,9
1,9,10
1,2,5,5,6,9
Bo,Di,Ada,Cy
Apple,fig,pear

Explanation

Array.prototype.sort with no argument converts the elements to strings and compares them as text. The numbers 10, 9, and 1 become "10", "9", and "1", which sort as 1, 10, 9. To sort numbers, pass a comparison function that returns a negative number, zero, or a positive number: (a, b) => a - b for ascending order. sort also changes the array in place.

Since ES2019 the sort is required to be stable, so equal elements keep their original order. That allows sorting by one key after another, or sorting objects by a field. Merge sort is a stable O(n log n) algorithm: split the array, sort both halves recursively, and merge by repeatedly taking the smaller front element, preferring the left on ties.

Explore the dry run
Guided walkthroughReady
1console.log([10, 9, 1].sort().join(","));
2console.log([10, 9, 1].sort((a, b) => a - b).join(","));
3
4function mergeSort(items) {
5 if (items.length <= 1) {
6 return items;
7 }
8 const middle = Math.floor(items.length / 2);
9 const left = mergeSort(items.slice(0, middle));
10 const right = mergeSort(items.slice(middle));
11 const merged = [];
12 let i = 0;
13 let j = 0;
14 while (i < left.length && j < right.length) {
15 merged.push(left[i] <= right[j] ? left[i++] : right[j++]);
16 }
17 return merged.concat(left.slice(i), right.slice(j));
18}
19console.log(mergeSort([5, 2, 9, 1, 5, 6]).join(","));
20
21const people = [
22 { name: "Ada", age: 30 },
23 { name: "Bo", age: 25 },
24 { name: "Cy", age: 30 },
25 { name: "Di", age: 25 },
26];
27people.sort((p, q) => p.age - q.age);
28console.log(people.map((p) => p.name).join(","));
29
30console.log(["pear", "Apple", "fig"].sort((a, b) => a.localeCompare(b)).join(","));

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Without a comparator the numbers are compared as strings, giving the wrong order.—
1,10,9
The comparator makes the order numeric.—
1,10,9
1,9,10
Take the smaller front element; on a tie take the left one so the sort is stable.—
1,10,9
1,9,10
The recursive merge sort returns a new sorted array.—
1,10,9
1,9,10
1,2,5,5,6,9
Sort by age. Equal ages keep their original order because the sort is stable.—
1,10,9
1,9,10
1,2,5,5,6,9
Bo and Di (25) come before Ada and Cy (30), each pair in the original order.—
1,10,9
1,9,10
1,2,5,5,6,9
Bo,Di,Ada,Cy
localeCompare sorts text the way people expect, ignoring the capital letter's code point.—
1,10,9
1,9,10
1,2,5,5,6,9
Bo,Di,Ada,Cy
Apple,fig,pear

Cost and common mistakes

Library sort is O(n log n). The merge sort here is O(n log n) time with O(n) extra space.

  • a - b as a comparator can give wrong results for values such as Infinity or NaN, and for huge numbers it can overflow in other languages. For safe numbers it is fine.
  • sort mutates the array. Copy it first with [...array] or toSorted (new in recent versions) if the original order must be kept.
  • A comparator that returns a boolean (a > b) instead of a number does not work reliably.

Try it yourself

How do you sort an array of numbers in descending order?

Reveal answer and reasoning

arr.sort((a, b) => b - a): swapping the operands reverses the order.

Further reading