JavaScript sorting and the default sort pitfall
Learn why [10, 9, 1].sort() is wrong, sort with comparators, and write a merge sort.
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(","));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
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(","));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
| Step / line | What happens | State after step | Output 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.