Why JavaScript sort() Puts 100 Before 9: Default String Order and the Comparator Contract
[10, 9, 1, 100].sort() returns [1, 10, 100, 9]. The default string comparison, the comparator contract, in-place mutation, undefined and holes, and toSorted(), per ECMA-262.
[10, 9, 1, 100].sort() returns [1, 10, 100, 9]. Without a comparator, Array.prototype.sort() converts elements to strings and compares those. This post follows the ECMA-262 editor’s draft (ECMAScript 2027) as checked on 11 October 2026.
The default sort compares strings
With no comparator, the spec’s CompareArrayElements converts both values with ToString and compares them with IsLessThan. String comparison walks the UTF-16 code units from the start; if one string is a prefix of the other, the shorter one comes first. "1" is a prefix of "10", and "100" vs "9" is decided at the first character, where 1 (U+0031) is less than 9 (U+0039).
[10, 9, 1, 100].sort(); // [1, 10, 100, 9]
[-1, -2, -10].sort(); // [-1, -10, -2]
["b", "a", "B", "A"].sort(); // ["A", "B", "a", "b"]
["e", "é", "f"].sort(); // ["e", "f", "é"]
Capitals (from U+0041) sort before lowercase (from U+0061), and é (U+00E9) lands after f (U+0066). This is code unit order, not dictionary order. Code units are covered in Why character counts differ between tools.
What a comparator returns
To sort numbers, pass a comparator.
[10, 9, 1, 100].sort((a, b) => a - b); // [1, 9, 10, 100]
[10, 9, 1, 100].sort((a, b) => b - a); // [100, 10, 9, 1]
The spec says comparator(x, y) should return a negative Number if x is less than y, a positive one if x is greater, and zero otherwise. The result goes through ToNumber and only its sign matters, so -37 or 5 work as well as -1 and 1. NaN is treated as zero.
String keys can’t be subtracted, so return the sign yourself:
users.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
An a - b comparator on BigInts throws a TypeError. Subtracting two BigInts is fine on its own, but the comparator then returns a BigInt, and CompareArrayElements applies ToNumber to that return value, which throws when given a BigInt.
[3n, 1n, 2n].sort((a, b) => a - b); // TypeError
[3n, 1n, 2n].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); // [1n, 2n, 3n]
Passing anything that isn’t a function, such as [1, 2].sort(null), also throws a TypeError. Only undefined means “no comparator”.
A consistent comparator
The spec expects a consistent comparator. It must return the same Number (never NaN) for the same pair of values and must not modify the object being sorted or any object on its prototype chain. Its “equal” results must be reflexive, symmetric and transitive, and its “less” and “greater” results transitive. If any of this fails, the sort order is implementation-defined: different engines or inputs may give different results without breaking the spec.
A common mistake is a comparator that returns a boolean:
arr.sort((a, b) => a > b); // broken comparator
true becomes 1 and false becomes 0. When a is less than b it still returns 0, meaning “equal”, so (1, 2) is equal while (2, 1) is greater, and symmetry breaks. The order is implementation-defined; a correct-looking result on some input guarantees nothing.
sort() mutates the array
sort() sorts in place and returns the same object.
const a = [3, 1, 2];
const b = a.sort((x, y) => x - y);
b === a; // true
a; // [1, 2, 3]
To keep the original, use toSorted(). It takes the same comparator and compares the same way, but returns a new array.
const a = [3, 1, 2];
const b = a.toSorted((x, y) => x - y);
a; // [3, 1, 2]
b; // [1, 2, 3]
undefined and holes
undefined always goes to the end without reaching the comparator. CompareArrayElements handles undefined before calling it, so your comparator never receives undefined as an argument.
A hole is an index below length with no property. sort() leaves holes out of the sort, writes the sorted values from the front, then deletes the remaining indices so the number of holes stays the same. Holes therefore end up after undefined.
const a = [3, undefined, , 1];
a.sort((x, y) => x - y); // [1, 3, undefined, <hole>]
3 in a; // false
toSorted() reads holes as undefined, so its result has none.
[3, undefined, , 1].toSorted((x, y) => x - y); // [1, 3, undefined, undefined]
These examples assume an ordinary array with no indexed properties inherited from its prototype. sort() checks each index with HasProperty, which also looks up the prototype chain, so if Array.prototype[2] existed, for example, index 2 would be read and sorted as a value instead of being skipped as a hole.
Sources
- ECMA-262 editor’s draft, 23.1.3.30 Array.prototype.sort, 23.1.3.30.1 SortIndexedProperties, 23.1.3.30.2 CompareArrayElements, 23.1.3.34 Array.prototype.toSorted
- ECMA-262 editor’s draft, 7.1.4 ToNumber, 7.2.12 IsLessThan
- ECMA-262 editor’s draft, 10.4.2 Array Exotic Objects
Section numbers are from the editor’s draft at the time of checking. Links and content were checked on 11 October 2026.