Lesson 31 of 80
sort() compares elements as strings by default — that's why [10, 2, 1] comes back [1, 10, 2]. Here's the comparator fix, and what "stable" actually buys you.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Array.prototype.sort() has a default behavior almost nobody expects: with no comparator, it converts every element to a string and sorts those strings in UTF-16 code-unit order. For strings that's usually fine. For numbers it's a trap — "10" sorts before "2" because "1" comes before "2" character by character, exactly like a dictionary would order them.
A comparator function fixes this by telling sort() how to compare two elements directly, as numbers (or dates, or objects, or whatever you actually have) instead of as text.
Since ES2019, the spec also guarantees sort() is stable: elements that compare as equal keep their original relative order. Before that, engines were allowed to shuffle equal elements around, and some did.
const nums = [10, 2, 1, 21, 3];
nums.sort();
// [1, 10, 2, 21, 3] -- "10" < "2" as strings, so 10 comes first
nums.sort((a, b) => a - b);
// [1, 2, 3, 10, 21] -- now it's actually numeric
The comparator contract: return a negative number if a should come before b, positive if a should come after, 0 if they're equal. a - b is the idiomatic ascending-numeric comparator; flip it to b - a for descending.
Stability matters the moment you sort by more than one thing. Say you've already got a list of users sorted alphabetically by name, and now you want to group them by role without losing that name order inside each group:
const users = [
{ name: "Bea", role: "admin" },
{ name: "Cid", role: "user" },
{ name: "Ana", role: "admin" },
{ name: "Dan", role: "user" },
];
// users is already alphabetical by name from an earlier sort.
users.sort((a, b) => a.role.localeCompare(b.role));
// [Ana(admin), Bea(admin), Cid(user), Dan(user)]
Because the sort is stable, users with the same role keep the relative order they arrived in — the earlier alphabetical sort survives inside each group for free. An unstable sort could legally scramble Ana and Bea relative to each other, and you'd have a bug that only shows up on some inputs, on some engines.
sort() with no arguments is a string sort, always. Even [10, 2, 1].sort() — JavaScript doesn't peek at the values and guess you meant numeric order. If you're sorting numbers, always pass a comparator.(a, b) => a - b works for numbers because subtraction happens to produce a signed number, but for anything else — strings, dates, custom objects — write the comparison explicitly (a.localeCompare(b), a.getTime() - b.getTime()).a - b can misbehave on floats with NaN or Infinity edge cases. If either operand can be NaN, subtraction gives NaN, which sort() treats as 0 (equal) — silently wrong. Prefer explicit comparisons (a < b ? -1 : a > b ? 1 : 0) when you can't guarantee clean numeric input.sort()'s default behavior is really a consequence of implicit string coercion — the same family of surprises covered in the equality and coercion lesson, just applied inside an array method instead of ==. Stability itself is a small instance of a bigger idea in software: an operation that promises to preserve relative order when nothing distinguishes its inputs, so composing it with earlier ordering doesn't destroy information you already had.