[10, 9, 1, 100, 25, 2].sort() does not give you 1, 2, 9, 10, 25, 100. I ran the usual ways to sort numbers and names in Node v22.23.4 (V8) and wrote down what each one does, so you can pick on purpose.
Numbers
| Call | Result |
|---|---|
.sort() |
[1, 10, 100, 2, 25, 9] |
.sort((a, b) => a - b) |
[1, 2, 9, 10, 25, 100] |
.sort((a, b) => a - b) on [3, NaN, 1, 2]
|
[3, NaN, 1, 2] (not sorted) |
MDN says the default sort converts elements to strings and compares UTF-16 code units, so "100" sorts before "2". The a - b comparator fixes numbers, but MDN notes it only works if the array has no NaN. My run shows what that looks like: nothing throws, the array just comes back in an order that is not sorted.
Names and file names
Input: ["Zoë", "zebra", "Éclair", "apple", "Apple", "eclair", "Zoe", "10 files", "2 files", "file 10", "file 2"]
| Method | Result |
|---|---|
default .sort()
|
10 files, 2 files, Apple, Zoe, Zoë, apple, eclair, file 10, file 2, zebra, Éclair |
localeCompare(b, "en") |
10 files, 2 files, apple, Apple, eclair, Éclair, file 10, file 2, zebra, Zoe, Zoë |
Intl.Collator("en", {numeric: true}) |
2 files, 10 files, apple, Apple, eclair, Éclair, file 2, file 10, zebra, Zoe, Zoë |
Three things to see:
- The default puts every capital letter before every lowercase one, and "Éclair" lands after "zebra", because the comparison is by code unit.
-
localeComparegives the order people expect for letters, but "file 10" still sorts before "file 2" and "10 files" before "2 files". - Adding
numeric: trueto a collator is what makes "2 files" come before "10 files". MDN listsnumericas off by default.
Equal or not: sensitivity
With sensitivity: "base", my run gave compare("Zoe", "Zoë") === 0, compare("a", "A") === 0 and compare("e", "é") === 0. That is what you want for search-as-you-type, and the wrong choice for a list where Zoe and Zoë are different people. MDN describes base as differing only in base letters, and says the default for sorting is variant.
The locale is part of the answer
["z", "ä", "a"] sorted with Intl.Collator("de") gave a, ä, z. With Intl.Collator("sv") it gave a, z, ä. Same data, same code, different order, because the locale decides where ä goes.
Holes and undefined
[3, undefined, 1, , 2].sort() gave [1, 2, 3, undefined, <empty>]: the length stayed 5, undefined went to the end, and the empty slot after it (MDN documents both rules). The comparator is never called for undefined.
What I would do
- For numbers, always pass a comparator, and filter out
NaNfirst if it can occur. - For text a person will read, use one
Intl.Collatorwith the user's locale andnumeric: truefor names with numbers in them. Create it once and passcollator.compare. - Use
toSorted()when you do not want the original array changed (my run:[3, 1, 2]stayed as it was). - Write one test with "Éclair", "file 2" and "file 10".
Not tested
Browsers (Node only), Safari and Firefox collation differences, locales other than en, de and sv, very large arrays, and sort stability. Collation depends on the engine's locale data, so check your target runtime.
Related: One number, three roundings.
Top comments (0)