Pass sort a compare function that looks at the property you care about. For numbers, return a.price - b.price. For text, return a.name.localeCompare(b.name). For dates, subtract the timestamps. A negative result puts a first, a positive one puts b first. Use toSorted instead of sort when the original order must survive.
| Key type | Ascending comparator | Descending |
|---|---|---|
| Number | (a, b) => a.age - b.age | (a, b) => b.age - a.age |
| Text | (a, b) => a.name.localeCompare(b.name) | (a, b) => b.name.localeCompare(a.name) |
| Text with numbers inside | a.code.localeCompare(b.code, undefined, { numeric: true }) | swap a and b |
| Date object | (a, b) => a.joined - b.joined | (a, b) => b.joined - a.joined |
| ISO date string | (a, b) => Date.parse(a.joined) - Date.parse(b.joined) | swap a and b |
| Boolean (true first) | (a, b) => Number(b.active) - Number(a.active) | swap a and b |
Updated
Sorting objects trips people up for three reasons: the default sort compares everything as text, uppercase letters sort before lowercase with a plain less-than check, and sort rewrites the array you pass it. The builder below lets you flip each of those switches on a small team list and watch the order change, with the matching comparator ready to copy.
| name | team | age | joined | code |
|---|---|---|---|---|
| maya | Design | 31 | 2024-03-02 | T-10 |
| Émile | Build | 27 | 2022-11-20 | T-2 |
| Ana | Design | 31 | 2025-01-15 | T-1 |
| zoë | Build | 42 | 2021-06-08 | T-21 |
| Bruno | Build | 27 | 2023-09-30 | T-3 |
Without a compare function every element is converted to a string and compared by UTF-16 code units, so the first character wins.
The default puts 100 before 25 because the text '1' comes before '2'.
[100, 25, 9].sort(); // [100, 25, 9]
[100, 25, 9].sort((a, b) => a - b); // [9, 25, 100]The || chain moves to the next key only when the previous comparison is 0. toSorted returns a new array.
const byTeamThenAge = (a, b) =>
a.team.localeCompare(b.team) || b.age - a.age;
const sorted = people.toSorted(byTeamThenAge);numeric: true orders T-2 before T-10, and sensitivity: 'base' ignores case and accents.
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
tickets.sort((a, b) => collator.compare(a.code, b.code));Pass the key names in order. A minus sign in front of a key sorts that key in descending order.
function sortBy(list, ...keys) {
const collator = new Intl.Collator(undefined, { numeric: true });
return list.toSorted((a, b) => {
for (const k of keys) {
const desc = k.startsWith("-");
const key = desc ? k.slice(1) : k;
const x = a[key], y = b[key];
const r = typeof x === "number" ? x - y : collator.compare(String(x), String(y));
if (r !== 0) return desc ? -r : r;
}
return 0;
});
}
sortBy(people, "team", "-age", "name");
sort only looks at the sign of the number you return. Below zero means a goes before b, above zero means b goes first, and zero means leave them as they are. The size of the number does not matter, which is why subtraction works for numbers and dates.
Subtraction does not work for text, since 'b' - 'a' is NaN. localeCompare returns a negative, zero or positive number for strings, so it slots straight into the same pattern.
Date objects subtract to a number of milliseconds, so a.joined - b.joined sorts oldest first. Dates stored as text in ISO format (2026-10-14) also sort correctly with localeCompare, because the year comes first, but other formats such as 14/10/2026 do not.
Undefined elements are always moved to the end and never passed to your function. A property that is undefined on an object is different: the object still reaches your comparator, so check for it yourself.
Call people.sort((a, b) => a.age - b.age) for a number property or people.sort((a, b) => a.name.localeCompare(b.name)) for a string. Swap a and b to reverse the direction.
Compare the first field and only fall through to the second when the first is a tie: (a, b) => a.team.localeCompare(b.team) || a.age - b.age. The || works because a tie returns 0, which counts as false.
Yes. Since ECMAScript 2019 sort must be stable, so items that compare as equal keep their earlier order. That is why sorting by name first and then by team gives a list grouped by team with names in order inside each group.
Yes. sort reorders the array in place and returns that same array. toSorted takes the same compare function but returns a new array and leaves the original order alone. It has been available across major browsers since July 2023.
A plain < or > check compares character codes, and every capital letter has a lower code than every lowercase one. localeCompare follows the language's alphabet instead, and with { sensitivity: 'base' } it also treats accents and case as equal.
Related: JavaScript array methods cheat sheet, JavaScript objects guide, Format dates in JavaScript, React table examples, JavaScript string methods.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.