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 value | Meaning |
|---|---|
| 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:
a | b | a - b | Result | Meaning |
|---|---|---|---|---|
| 25 | 100 | -75 | Negative | 25 comes before 100 ✅ |
| 100 | 9 | 91 | Positive | 100 comes after 9 ✅ |
| 5 | 5 | 0 | Zero | Keep 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
acomes beforeb - a positive number if
acomes afterb - 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", usesort((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 sort | Comparator |
|---|---|
| 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 original | arr.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. Usea - bfor ascending andb - afor 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.