JavaScript Sort Table

PatternWhat it doesField note
arr.sort()Default sortConverts to STRINGS first - 10 sorts before 9
arr.sort((a,b) => a-b)Numeric ascendingThe comparator contract: negative keeps order, positive swaps
arr.sort((a,b) => b-a)Numeric descendingFlip the subtraction, flip the order
arr.sort((a,b) => a.name.localeCompare(b.name))String compareLocale-aware; second arg sets language
[...arr].sort(fn)Sort a copysort mutates in place - spread first or use toSorted
arr.toSorted(fn)Non-mutating sortThe ES2023 fix; same comparator, new array
arr.sort(() => Math.random() - 0.5)BIASED shuffleThe famous anti-pattern - use Fisher-Yates
arr.sort((a,b) => a.d - b.d || a.n.localeCompare(b.n))Multi-key sortThe || chain: later keys break earlier ties
Reference: the MDN Array.sort reference. The three facts behind most sort bugs: default sort compares STRINGS (so 10 comes before 9), it MUTATES the array in place (spread or toSorted when the original order matters), and the comparator contract is arithmetic - negative means keep, positive means swap. Bottom line: numbers want a-b, text wants localeCompare, multiple keys chain with ||, and the random-comparator shuffle is a biased anti-pattern - real shuffles need Fisher-Yates. Related tools: JS array methods table, loops table, line sorter for the no-code version, and equality table for the comparison semantics underneath.

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

  1. Pick the pattern by data type: numbers subtract, strings localeCompare, objects project the field first then subtract or compare.
  2. Decide mutation: sort in place when the array is yours to reorder; spread or toSorted when callers hold references.
  3. 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.

Related tools