Bitsy Explains
Home/Cursed JavaScript/Lesson 4

Why sort() Puts 10 Before 2

Why [10, 1, 2].sort() gives [1, 10, 2], how to sort numbers with a compare function, and why sort() changes your original array.

Lesson 4 of 103 min readVideo
Video thumbnail: Why sort() Puts 10 Before 2 Watch the 90-second video
Explain like I'm five

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 10 becomes "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 a goes first.
  • Above zero means b goes 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.

Common mistake

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

Which call sorts [10, 1, 2] from biggest to smallest?