An array of numbers sorted by sort with no precaution comes back as [1, 10, 100, 9]. That is not a bug, it is the documented behavior, and it is probably the most widely met trap in the language.
Two things are worth knowing before writing a sort: sort changes the original array, and it compares text until it is told how to compare.
Definition
sort reorders the items of an Array in place and hands back that same array, not a copy. With no argument, each item is converted to a string, and the strings are then compared character by character.
const scores = [10, 9, 1, 100];
console.log(scores.sort()); // [ 1, 10, 100, 9 ]
console.log(scores); // the original array was changed
console.log([10, 9, 1, 100].sort((a, b) => a - b)); // [ 1, 9, 10, 100 ]"10" comes before "9" for the same reason "ab" comes before "b" in a dictionary: the first character decides. The comparison function restores the expected order.
The comparison function
It receives two items and returns a number. Only its sign matters, the exact value is irrelevant.
- Negative the first item goes before the second.
- Zero both keep their relative order.
- Positive the second item goes before the first.
const products = [
{ name: "Keyboard", price: 89 },
{ name: "Mouse", price: 29 },
{ name: "Monitor", price: 249 },
];
products.sort((a, b) => a.price - b.price);
console.log(products.map((p) => p.name)); // [ 'Mouse', 'Keyboard', 'Monitor' ]
products.sort((a, b) => b.price - a.price); // most expensive firstSwapping a and b flips the sort, which saves a call to reverse afterwards.
Sorting text, and sorting without changing anything
On accented words, the default comparison puts "Zoé" before "école", because it compares character codes rather than letters. localeCompare knows the rules of the language.
const words = ["éclair", "avion", "zèbre", "École"];
console.log(words.sort((a, b) => a.localeCompare(b, "fr")));
// [ 'avion', 'éclair', 'École', 'zèbre' ]
const original = [3, 1, 2];
const sorted = [...original].sort((a, b) => a - b);
console.log(original); // [ 3, 1, 2 ], untouchedCopying with Spread (...) before sorting is the habit to build as soon as the data is displayed somewhere else. The newer toSorted method does the same in a single step and leaves the original alone.
Frequently asked questions
Why does [10, 9, 1] turn into [1, 10, 9]?
Because with no comparison function, the numbers are converted to text before being compared. "10" starts with the character "1", which comes before "9". Passing (a, b) => a - b settles the question for good on numbers.
Is the sort stable?
Yes. Two items the comparison judges equal keep their original order, and the standard has guaranteed that since 2019. It allows sorting in two passes: by name first, by price second, and equal prices will still be ordered by name.
How do you sort on several criteria at once?
By chaining comparisons with the || operator, which moves to the next criterion whenever the previous one returns zero: (a, b) => a.price - b.price || a.name.localeCompare(b.name, "en"). Sorting lists, dashboards and catalogs is covered in detail in the JavaScript course.