skip to content

Why does [10, 9, 1].sort() return [1, 10, 9] in JavaScript, and how do you sort an array of numbers correctly?

level: juniorimportance: must knowfreq 85%

answer

  1. elements are not compared as numbers
  2. default converts each element first
  3. string order, UTF-16 code units
  4. "10" beats "9" at the first character
  5. supply (a, b) => a - b

basics

~20 s

Array.prototype.sort called with no comparator converts every element to a string and compares those strings, so "10" sorts before "9". Pass a numeric comparator, arr.sort((a, b) => a - b), to order numbers by value.

solid answer

~50 s

`Array.prototype.sort` with no argument does not compare numbers as numbers. The spec says that when the comparator is `undefined`, each element is converted with `ToString` and the resulting strings are compared by UTF-16 code unit. `"10"` and `"9"` differ at the first character, `'1'` (U+0031) versus `'9'` (U+0039), so `"10"` wins and you get `[1, 10, 9]`. The fix is to supply a comparator: `arr.sort((a, b) => a - b)` for ascending, `(b, a) => ...` or `(a, b) => b - a` for descending. Two other things to say in the same breath: `sort` mutates the array in place and returns that same array reference, so `[...arr].sort((a, b) => a - b)` is how you keep the original intact; and `undefined` elements are always moved to the end without ever being passed to your comparator.

code

javascript · 5 lines
javascript
const nums = [10, 9, 1, 700, 80];

console.log([...nums].sort());               // [1, 10, 700, 80, 9]
console.log([...nums].sort((a, b) => a - b)); // [1, 9, 10, 80, 700]
console.log([...nums].sort((a, b) => b - a)); // [700, 80, 10, 9, 1]

go deeper

for a junior

Know that a bare sort() compares elements as strings and that numbers need a comparator. Be able to write arr.sort((a, b) => a - b) from memory and predict the output of [10, 9, 1].sort().

for a middle

Explain the mechanism rather than the rule: ToString conversion followed by UTF-16 code-unit comparison, and the negative/zero/positive contract the comparator satisfies. Mention in-place mutation and where undefined lands.

for a senior

Talk about the bug this causes in real code: a shared or cached array quietly reordered by a sort you thought returned a copy, and a numeric comparator producing NaN on dirty data. Say how you would guard it in review.

for a principal

Frame it as an API-design and data-hygiene call: whether ordering belongs at the data layer, in the query, or in the view; and whether your codebase standardises on sorting copies so that shared state is never reordered as a side effect.

## The behaviour `Array.prototype.sort()` is one of the few built-ins whose default behaviour surprises almost everyone the first time: ```js console.log([10, 9, 1].sort()); // [1, 10, 9] console.log([80, 9, 700].sort()); // [700, 80, 9] ``` This is not a bug and not an engine quirk — it is exactly what the specification requires. ## Why: ToString then code-unit comparison `sort` takes an optional comparator. When you call it with no argument (the comparator is `undefined`), the abstract sorting operation converts each element to a string with the `ToString` operation and compares those two strings, not the original values. String comparison in JavaScript is not alphabetical or linguistic — it walks the two strings and compares UTF-16 code units at the first position where they differ. For `10` and `9`, the strings are `"10"` and `"9"`. Position 0 already decides it: `'1'` is code unit 0x31 and `'9'` is 0x39, so `"10"` is "less than" `"9"`. Length never enters into it. That is why `700` beats `80` beats `9` in the second example. The default exists so that `sort()` has a defined result for arrays of mixed types without you telling it anything. It is a lowest-common-denominator rule, not a guess at your intent. ## The fix: a comparator A comparator is a two-argument function whose sign tells `sort` the order: negative means "`a` before `b`", zero means "treat as equivalent", positive means "`b` before `a`". ```js const nums = [10, 9, 1]; nums.sort((a, b) => a - b); // [1, 9, 10] ascending nums.sort((a, b) => b - a); // [10, 9, 1] descending ``` Subtraction is the idiomatic numeric comparator because it produces the right sign for free. It is safe for ordinary finite numbers. Two cases where subtraction is the wrong tool: values that are not numbers (a missing field makes `a - b` evaluate to `NaN`), and values where you want an explicit three-way comparison. For those, write the comparison out: ```js const byValue = (a, b) => (a < b ? -1 : a > b ? 1 : 0); ``` That form works for anything the relational operators order sensibly and never produces `NaN`. ## sort mutates in place `sort` reorders the receiver and returns *that same array*, not a copy: ```js const original = [3, 1, 2]; const result = original.sort((a, b) => a - b); console.log(result === original); // true console.log(original); // [1, 2, 3] — the original changed ``` This is a routine source of bugs when the array is shared: a cached list, an object property, or a value another part of the program still holds a reference to. If callers must not see the reordering, sort a copy — `[...arr].sort(cmp)` or `arr.slice().sort(cmp)`. ## undefined values and holes `sort` treats `undefined` specially. All `undefined` elements are moved to the end of the array and your comparator is **never called** with them, so you cannot make them sort first by writing clever comparator logic. In a sparse array, the empty slots end up after the `undefined` values. If you need a different policy for missing data, filter or normalise before sorting: ```js [3, undefined, 1].sort((a, b) => a - b); // [1, 3, undefined] ``` ## Things that look like fixes but are not - `arr.sort(Number)` — `sort` will call `Number` with two arguments and use the result of `Number(a)` as the comparison value, which is nonsense; the ordering it produces is not a numeric sort. - `arr.sort((a, b) => a > b)` — returns a boolean, which coerces to `1` or `0` and never to a negative number, so the comparator can never say "`a` first" and the result is unreliable. - Relying on the array "looking sorted" for small inputs — engines may take different code paths for short arrays, so a broken comparator can pass a three-element test and fail on a hundred. ## What to say in an interview Name the mechanism (`ToString` plus UTF-16 code-unit comparison), give the fix (`(a, b) => a - b`), and volunteer the two side facts that separate a memoriser from someone who has used it: in-place mutation of the receiver, and `undefined` sorting to the end.

  • Where do undefined values end up, and can a comparator change that?
    All `undefined` elements are moved to the end of the array, and the comparator is never invoked for them, so no comparator logic can place them elsewhere. In a sparse array the empty slots come after the `undefined` values. If missing entries need a different position, normalise or filter the data before sorting.
  • Is (a, b) => a - b always a safe numeric comparator?
    For ordinary finite numbers, yes. It breaks when a value is not a number — a missing or string field makes the subtraction `NaN`, which `sort` treats as "equal", silently leaving those elements wherever they happen to be. `Infinity - Infinity` is also `NaN`. An explicit `a < b ? -1 : a > b ? 1 : 0` never has that failure mode.
  • How would you sort an array without changing the caller's copy?
    Sort a copy: `[...arr].sort(cmp)` or `arr.slice().sort(cmp)`. `sort` mutates the receiver and returns that same reference, so assigning its result to a new variable gives you two names for one array — a classic bug when the array is a shared cache or an object property that other code still reads.

It is like filing house numbers alphabetically instead of numerically: 10 files under "1", so it lands in front of 9.

saying these in an interview costs you the question

  • Claiming sort() compares numbers numerically by default
  • Saying sort returns a new array and leaves the original alone
  • Thinking the default order is alphabetical or locale-aware
  • Explaining it as "shorter strings sort first"
  • Assuming a comparator can move undefined values to the front

context