Sorting & Custom Comparators

It is sports day at school. 🏃‍♀️ The race is over, and the teacher has everyone's timings written in a notebook, in random order. To announce the winners, she arranges the students from fastest to slowest.

Arranging items in a particular order is called sorting. We sort things all the time:

  • Products on a shopping app: price low to high 🛒
  • Contacts on your phone: A to Z 📱
  • A cricket leaderboard: highest runs first 🏏

In JavaScript, arrays have a built-in method for this: sort().


Sorting Strings with sort()

By default, sort() arranges items in alphabetical order (A → Z). Easy! 😊

Key points:

  • sort() changes the original array. It is a mutating method. ✍️
  • It also returns the same sorted array.

⚠️ The Big Surprise: Sorting Numbers

Now let us sort some marks:

That's wrong! What happened?

By default, sort() treats every item as text. So it compares them letter by letter, just like words in a dictionary:

"100"  → starts with "1"
"1000" → starts with "1"
"25"   → starts with "2"
"3"    → starts with "3"
"9"    → starts with "9"

Since "1" comes before "2" in a dictionary, "100" and "1000" come first, even though 25 is smaller than 100.

It is like arranging house numbers by reading only the first digit. House 100 would come before house 25! 🏠

To sort numbers correctly, we need to tell sort() how to compare. For this, we use a custom comparator.


What Is a Custom Comparator?

A comparator is a small function that you give to sort(). It tells JavaScript how to compare two items.

array.sort((a, b) => {
  // return a number
});

sort() picks two items at a time (a and b) and asks your function: "Which one should come first?"

Your function answers by returning a number:

Return valueMeaning
Negative (less than 0)a comes before b
Positive (more than 0)a comes after b
Zero (0)Keep their order as it is

Think of the comparator as the referee 🧑‍⚖️ in a match between two items. For every pair, the referee decides who goes first.


Sorting Numbers Correctly

Smallest to largest (ascending) ⬆️

Why does a - b work? Let us check a few pairs:

aba - bResultMeaning
25100-75Negative25 comes before 100 ✅
100991Positive100 comes after 9 ✅
550ZeroKeep the order

The smaller number always gives a negative result when it is a, so it moves to the front.

Largest to smallest (descending) ⬇️

Just swap a and b:

Easy trick to remember:

  • a - b → ascending (small to big) ⬆️, "a comes first, like the alphabet"
  • b - a → descending (big to small) ⬇️

Real example: price low to high 🛒


Sorting Strings the Right Way

The capital letter problem

The default sort() puts all capital letters before small letters.

"Zoya" comes before "neha", because in the computer's character list, all capital letters (A–Z) come before small letters (a–z).

✅ Fix: use localeCompare()

localeCompare() compares two strings the way a human would, like in a real dictionary.

a.localeCompare(b) returns:

  • a negative number if a comes before b
  • a positive number if a comes after b
  • 0 if they are the same

That is exactly what a comparator needs! 🎯

Z to A (reverse alphabetical)

Sorting strings that contain numbers

What about file names like "Chapter 10" and "Chapter 2"?

The { numeric: true } option tells JavaScript to read the numbers inside the text as real numbers.


Sorting Objects

In real apps, you usually sort objects, like products, students, or orders, based on one of their properties.

By a number property

Here, a and b are whole objects, so we compare their .price values.

By a text property

Real example: a cricket leaderboard 🏏


Sorting by Multiple Properties (Tie-Breaker) 🏅

What if two students have the same marks? We need a tie-breaker, like sorting them by name.

Rule: Sort by marks (highest first). If the marks are equal, sort by name (A → Z).

A shorter way with ||

How it works: If the marks are equal, b.marks - a.marks gives 0. Since 0 is falsy, || moves on and uses the name comparison instead. Smart! 🧠


Sorting by a Custom Order

Sometimes the order is not alphabetical or numeric. For example, clothing sizes should go S → M → L → XL.

The idea: Instead of comparing the sizes directly, we compare their positions in our own sizeOrder list.

Another example: order status in a delivery app 🚚


Sorting Dates 📅

Dates can be subtracted just like numbers, so the same a - b trick works.


Sorting Without Changing the Original

Since sort() changes the original array, you may lose the original order. There are two safe ways to avoid this.

1. Copy first, then sort

2. Use toSorted() (modern and easy)

toSorted() works just like sort(), but it returns a new sorted array and doesn't touch the original.

Tip: Use toSorted() whenever you want to show a sorted list but keep the original data unchanged, like a "Sort by price" button on a shopping page.


Reversing an Array

Want the order backwards? Use reverse() or toReversed().

⚠️ reverse() only flips the current order. It does not sort. To get "largest first", use sort((a, b) => b - a).


Stable Sorting: Equal Items Keep Their Order

When two items are equal (the comparator returns 0), JavaScript keeps them in the same order as before. This is called a stable sort.

Amit was before Ravi in the original list, so he stays before Ravi after sorting. The same goes for Neha and Sara. 👍


Common Mistakes to Avoid

❌ Mistake 1: Sorting numbers without a comparator

❌ Mistake 2: Returning true or false from the comparator

The comparator must return a number (negative, positive, or zero), not true/false.

❌ Mistake 3: Forgetting that sort() changes the original

Fix: Use toSorted() or [...original].sort().

❌ Mistake 4: Subtracting strings

For strings, always use localeCompare(), not -.


Quick Summary Table

What to sortComparator
Strings A → Z(a, b) => a.localeCompare(b)
Strings Z → A(a, b) => b.localeCompare(a)
Numbers small → big(a, b) => a - b
Numbers big → small(a, b) => b - a
Objects by number(a, b) => a.price - b.price
Objects by text(a, b) => a.name.localeCompare(b.name)
With a tie-breaker(a, b) => b.marks - a.marks || a.name.localeCompare(b.name)
Custom order(a, b) => order.indexOf(a) - order.indexOf(b)
Dates, earliest first(a, b) => a.date - b.date
Without changing the originalarr.toSorted(...) or [...arr].sort(...)

Key Takeaways

  • sort() arranges array items and changes the original array.
  • By default, it sorts as text, which gives wrong results for numbers ([1, 10, 2]).
  • A comparator (a, b) => ... returns a negative, positive, or zero number to decide the order. Use a - b for ascending and b - a for descending.
  • Use localeCompare() for strings, and combine rules with || for tie-breakers.
  • Use toSorted() (or copy first) when you want a sorted list without changing the original.