Sorting Arrays
Sorting looks simple — call arr.sort() and you are done. In practice the default behaviour surprises almost every newcomer, the method mutates the array, and modern JavaScript has added a non-mutating alternative. This page covers the four things you need to know: the quirky default, how to write a compare function, what stable means, and the new toSorted / toReversed methods.
The default sort is alphabetical — even for numbers
Without a compare function, sort converts every element to a string and orders the strings by UTF-16 code units. Numbers, dates, objects — all stringified first. That is almost never what you want for numbers.
[10, 1, 21, 2, 100].sort(); // [1, 10, 100, 2, 21] — string order! ["banana", "Apple", "cherry"].sort(); // ["Apple", "banana", "cherry"] — uppercase comes first in UTF-16
Writing a compare function
A compare function takes two elements a and b and returns:
Negative number —
ashould come beforeb.Positive number —
ashould come afterb.Zero — keep their relative order (the sort is stable, see below).
const nums = [10, 1, 21, 2, 100];
nums.sort((a, b) => a - b); // ascending: [1, 2, 10, 21, 100]
nums.sort((a, b) => b - a); // descending: [100, 21, 10, 2, 1]
// Strings, case-insensitive
const words = ["Banana", "apple", "Cherry"];
words.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
// ["apple", "Banana", "Cherry"]
// By a key
const users = [
{ name: "Lin", age: 31 },
{ name: "Ada", age: 25 },
{ name: "Pedro", age: 28 },
];
users.sort((a, b) => a.age - b.age);
// Ada(25) → Pedro(28) → Lin(31)sort() mutates — and returns the same array
Like reverse and splice, sort changes the array in place and also returns it (a convenience that often hides the mutation).
const xs = [3, 1, 2]; const ys = xs.sort(); // ys is the SAME array as xs ys[0] = 99; xs; // [99, 2, 3] — surprise! // To sort without mutating: copy first const zs = [...xs].sort(); // or use toSorted (next section)
Stable sort
A sort is stable if elements that compare equal keep their relative order. Modern JavaScript guarantees stability (since ES2019). That matters when you sort by a partial key.
const records = [
{ name: "Ada", country: "UK" },
{ name: "Tina", country: "UK" },
{ name: "Lin", country: "TW" },
{ name: "Pedro", country: "BR" },
];
records.sort((a, b) => a.country.localeCompare(b.country));
// Stable: within "UK", Ada still appears before Tina
// because that was their original order.toSorted, toReversed, toSpliced, with
ES2023 added four non-mutating siblings of the classic mutators. They return a new array and leave the original alone — exactly what frameworks like React want.
const xs = [3, 1, 2]; const sorted = xs.toSorted((a, b) => a - b); // [1, 2, 3]; xs unchanged const reversed = xs.toReversed(); // [2, 1, 3]; xs unchanged const replaced = xs.with(0, 99); // [99, 1, 2]; xs unchanged const trimmed = xs.toSpliced(1, 1); // [3, 2]; xs unchanged xs; // still [3, 1, 2]
These methods are available in all modern browsers and Node 20+. If you target older environments, fall back to spread-then-mutate: [...xs].sort(cmp).
Locale-aware sorting
Plain string comparison breaks for non-English text. Intl.Collator or String.prototype.localeCompare handles accents, case rules, and digits-in-text the way humans expect.
const items = ["item 10", "item 2", "item 1", "ítem 3"];
// Naive
[...items].sort();
// ["item 1", "item 10", "item 2", "ítem 3"] — wrong
// Locale-aware with numeric handling
const collator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: "base",
});
[...items].sort(collator.compare);
// ["item 1", "item 2", "ítem 3", "item 10"]Sorting cheat sheet
Default
sort()is string order — pass a compare function for everything else.Ascending numbers:
(a, b) => a - b. Descending:(a, b) => b - a.For strings, prefer
a.localeCompare(b)to</>.Sort is stable in modern engines — chain compares with
||or sort in passes.sortmutates; usetoSorted(or[...arr].sort()) when you need a copy.