Sort an Array of Objects in JavaScript

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.

Comparator for each kind of key
Key typeAscending comparatorDescending
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 insidea.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.

Sample list used by the comparator builder (original order)
nameteamagejoinedcode
mayaDesign312024-03-02T-10
ÉmileBuild272022-11-20T-2
AnaDesign312025-01-15T-1
zoëBuild422021-06-08T-21
BrunoBuild272023-09-30T-3

Why plain sort() gets numbers wrong

Without a compare function every element is converted to a string and compared by UTF-16 code units, so the first character wins.

Default order versus a numeric comparator

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]

Multi-key, locale-aware and non-mutating sorts

Team A to Z, then oldest first, without touching the original

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);

One Intl.Collator for a long list

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));

A reusable sortBy helper

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");

What the compare function really returns

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.

Dates, missing values and mixed types

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.

How it works

  1. Choose the property to sort by: name, age, start date or a ticket code that mixes letters and digits.
  2. Pick ascending or descending, and for text keys choose plain comparison or localeCompare with the numeric option.
  3. Add a second key to break ties, for example team first, then name.
  4. Copy the generated code. The table shows the order your comparator produces right now.

Tips

Frequently asked questions

How do I sort an array of objects by a property?

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.

How do I sort by two fields?

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.

Is JavaScript sort stable?

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.

Does sort change the original array?

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.

Why are lowercase names sorted after uppercase ones?

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.

More in Guides

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.

Product

Who It's For

Features

Resources

Company

View Sitemap