By default, sort lines things up like words in a dictionary. It turns every number into text and compares the text letter by letter. The text "10" starts with "1", and "1" comes before "2", so 10 lands before 2, the same way "apple" comes before "banana".
The curse
console.log([10, 1, 2].sort()); // prints: [ 1, 10, 2 ]
console.log(["b", "a", "c"].sort()); // prints: [ 'a', 'b', 'c' ]
Small letters sort just fine. Numbers come out in an order that looks random until you know the rule.
What sort does by default
When you call sort() with nothing inside, JavaScript does three things.
- It turns each item into a string, so
10becomes"10". - It compares those strings character by character, using each character's code number in Unicode.
- It decides the order at the first character that differs.
"10" and "2" already differ at the first character, "1" against "2", and "1" has the smaller code. So "10" goes first, and the rest of the string never gets a look.
Sort works this way because it was made for any kind of list, and text is the one thing every value can be turned into. The same rule explains a surprise with words.
console.log(["b", "B", "a"].sort()); // prints: [ 'B', 'a', 'b' ]
Capital letters have smaller codes than small letters, so every capital goes before every lowercase letter. For friendly word order, use (a, b) => a.localeCompare(b).
How to break the curse
Give sort a little compare function. Sort hands it two items, a and b, and looks at the number you return.
- Below zero means
agoes first. - Above zero means
bgoes first. - Zero means they keep their order.
const scores = [10, 1, 2];
console.log(scores.sort((a, b) => a - b)); // prints: [ 1, 2, 10 ]
console.log(scores.sort((a, b) => b - a)); // prints: [ 10, 2, 1 ]
a - b is below zero when a is smaller, so the smallest number goes first. b - a flips that and puts the biggest first. The same idea works for objects, for example (a, b) => a.age - b.age to line kids up by age.
Bonus twist: sort changes your array
Unlike map and filter, sort does not make a new array. It rearranges the one you have, and returns that same array.
const nums = [3, 1, 2];
nums.sort((a, b) => a - b);
console.log(nums); // prints: [ 1, 2, 3 ]
const prices = [30, 10, 20];
const sorted = prices.toSorted((a, b) => a - b);
console.log(prices); // prints: [ 30, 10, 20 ]
console.log(sorted); // prints: [ 10, 20, 30 ]
If you need the original order later, use toSorted. It returns a new sorted array and leaves the original alone. It is fairly new (Node 20 and current browsers), so in older code you will see [...prices].sort(...) instead, which copies the array first and then sorts the copy.
Rule of thumb
Sorting numbers? Always give sort a compare function, like (a, b) => a - b.
Returning true or false from the compare function, like (a, b) => a > b. Sort needs a negative, positive or zero number, and a boolean only ever becomes 1 or 0, so the order can come out wrong. Return a - b instead.
Check yourself
b - a is below zero when b is smaller, which puts the bigger number first. Plain .sort() compares the numbers as text.