JavaScript Sort Table
| Pattern | What it does | Field note |
|---|---|---|
arr.sort() | Default sort | Converts to STRINGS first - 10 sorts before 9 |
arr.sort((a,b) => a-b) | Numeric ascending | The comparator contract: negative keeps order, positive swaps |
arr.sort((a,b) => b-a) | Numeric descending | Flip the subtraction, flip the order |
arr.sort((a,b) => a.name.localeCompare(b.name)) | String compare | Locale-aware; second arg sets language |
[...arr].sort(fn) | Sort a copy | sort mutates in place - spread first or use toSorted |
arr.toSorted(fn) | Non-mutating sort | The ES2023 fix; same comparator, new array |
arr.sort(() => Math.random() - 0.5) | BIASED shuffle | The famous anti-pattern - use Fisher-Yates |
arr.sort((a,b) => a.d - b.d || a.n.localeCompare(b.n)) | Multi-key sort | The || chain: later keys break earlier ties |
Array.sort is two surprises wearing one method: it converts to strings by default (so 10 sorts before 9), and it mutates the array in place (so your original order is gone). The table below is the working eight patterns - the comparator forms that cover real sorting, plus the two modern fixes and one famous anti-pattern.
Bottom line: the comparator contract is arithmetic, not boolean - return a negative number to keep a before b, positive to swap, zero for ties. Numbers want (a, b) => a - b, text wants localeCompare for correct language ordering, and multiple keys chain with the || operator: sort by the first key, break ties with the second.
The honest part: two habits prevent every production sort bug. First, sort mutates - use [...arr].sort() or the ES2023 toSorted() when the original order matters, because callers rarely expect their array to be reordered under them. Second, never shuffle with sort(() => Math.random() - 0.5): the comparator is supposed to be consistent, and violating that biases the result - provably uneven, and worse on longer arrays. Real shuffles use Fisher-Yates.
How to use
- Pick the pattern by data type: numbers subtract, strings localeCompare, objects project the field first then subtract or compare.
- Decide mutation: sort in place when the array is yours to reorder; spread or toSorted when callers hold references.
- Chain multi-key orders with || - the first comparison that returns non-zero decides, later expressions break ties.
Frequently asked questions
Why does [10, 9, 1].sort() put 10 first?
Because the default comparator converts every element to a string and compares Unicode order: "10" < "9" because "1" comes before "9" character-by-character. Sorting is stringwise unless you pass a comparator - so any array of numbers must call sort((a, b) => a - b). The subtraction form is not style; it implements the contract: negative keeps the order, positive swaps, zero ties.
Does sort() change my original array?
Yes - sort mutates in place AND returns the same array, which makes const sorted = arr.sort() look harmless while arr has silently reordered. Two fixes: spread first ([...arr].sort(fn)), or use toSorted(fn) - the ES2023 non-mutating twin. The same in-place family includes reverse() and splice(); when a function receives an array it did not create, treat mutation as a side effect the caller never asked for.
How do I sort by multiple fields?
Chain comparators with ||: (a, b) => a.lastName.localeCompare(b.lastName) || a.age - b.age sorts by last name, and when names tie, age breaks the tie. The || short-circuits: if the first comparison is non-zero it is the answer; zero means tie, so the next expression runs. Order the keys from most to least significant, exactly as you would read the requirement aloud - the code mirrors the sentence.
Why is sort(() => Math.random() - 0.5) a bad shuffle?
Because sort expects a CONSISTENT comparator, and random values are inconsistent between comparisons of the same pair - the underlying algorithm's guarantees collapse, and the result is measurably biased: some orders are far more likely than others, and longer arrays skew harder. A fair shuffle needs Fisher-Yates: walk from the end, swap each position with a uniformly random earlier position - every permutation exactly equally likely. For display jokes the bias is invisible; for card games, raffles and A/B assignment it is a real fairness bug.