Topic 5.6
Sorting
In one line
sort reorders an array in place and, without a comparator, sorts everything as text (so [10, 1, 5] becomes [1, 10, 5]); pass (a, b) => a - b for numbers, use toSorted and toReversed to get a sorted copy, and rely on sort being stable since ES2019.
Think of it like this
Lining up a class by height. The teacher compares two children at a time: "you two, who's shorter?" and swaps them if needed, until the line is in order. The teacher needs a rule for each comparison. If the rule were "alphabetical by the first digit of your height", a child of 100 cm would stand before one of 95 cm, because "1" comes before "9". That silly rule is exactly what JavaScript's sort uses when you don't give it one.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Sort
- Put items in order by some rule.
- Comparator
- A function that compares two items and returns a negative, zero or positive number to say which goes first.
- Ascending
- From smallest to largest (1, 2, 3 or A to Z).
- Descending
- From largest to smallest (3, 2, 1 or Z to A).
- Stable sort
- A sort that keeps equal items in the order they were in before.
- localeCompare
- A string method that compares text the way a given language orders it.
- In place
- Changing the original array instead of making a new one.
- UTF-16 code unit
- The number the computer uses to store each character; the default sort compares these numbers.
Step by step
01Try the default
Without a comparator, numbers become text first. "1", "10", "100" all start with 1, so they come before "5".
[10, 1, 5, 100].sort(); // [ 1, 10, 100, 5 ]02Give it a rule
a - b is negative when a is smaller, so smaller numbers go first.
[10, 1, 5, 100].sort((a, b) => a - b); // [ 1, 5, 10, 100 ]
[10, 1, 5, 100].sort((a, b) => b - a); // [ 100, 10, 5, 1 ]03Understand what the comparator answers
The sort calls your function with pairs of items and reads only the sign of the answer.
04Sort objects by a property
Compare the property you care about. For text, use localeCompare.
products.sort((a, b) => a.price - b.price);
people.sort((a, b) => a.name.localeCompare(b.name));05Don't change the original
sort and reverse mutate. toSorted and toReversed return copies.
const ranked = scores.toSorted((a, b) => b - a);
const newestFirst = posts.toReversed();Try it yourself
- 1
Sort prices high to low
In the first example, add
const prices = [99, 5, 250, 40];and print them from highest to lowest without changingprices. Predict the array. - 2
Sort by name length
In the third example, add a button that sorts by the length of the name, shortest first. What should the comparator return? Which names tie, and what order do they keep?
- 3
Watch the comparator
Add
console.log(a, b)inside(a, b) => a - bin the first example. Predict how many times it runs for 5 numbers (it's more than 4), then count.
Code & diagrams
`[...nums]` makes a copy so each line starts from the same order. `localeCompare` puts `apple` next to `Apple` the way a dictionary would; `undefined` always goes last.
Console
(nothing was printed)Expected console output
[ 1, 10, 100, 25, 5 ]
[ 1, 5, 10, 25, 100 ]
[ 100, 25, 10, 5, 1 ]
[ 'Apple', 'apple', 'banana', 'cherry' ]
[ 'apple', 'Apple', 'banana', 'cherry' ]
[ 1, 3, undefined ]`sort` returned the very same array it changed. `toSorted` and `toReversed` (ES2023) leave `scores` alone; `reverse` changes it.
Console
(nothing was printed)Expected console output
true [ 1, 2, 3 ]
[ 40, 90, 70 ] [ 90, 70, 40 ]
[ 70, 90, 40 ] [ 40, 90, 70 ]
[ 70, 90, 40 ]Sorting by grade only is stable: Ravi stays before Kabir and Asha before Meera, as in the original list. `||` adds a second key used only when grades are equal.
Preview
Console
Nothing logged yet.Expected console output
[ 'A Ravi', 'A Kabir', 'B Asha', 'B Meera' ]
[ 'A Kabir', 'A Ravi', 'B Asha', 'B Meera' ]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Sorting numbers without a comparator
Sort scores with scores.sort() where scores is [9, 80, 100].
Break #2
Returning a boolean from the comparator
Write nums.sort((a, b) => a > b).
Break #3
Sorting someone else's array
Inside a function, write function top3(list) { return list.sort((a, b) => b - a).slice(0, 3); } and call it on your main list.
Myth vs fact
Myth
sort() with no arguments sorts numbers correctly.
Fact
It sorts everything as text. [2, 10, 1].sort() is [1, 10, 2]. Pass (a, b) => a - b.
Myth
sort returns a new sorted array.
Fact
It sorts the original in place and returns that same array. Use toSorted (ES2023) for a copy.
Myth
JavaScript's sort might shuffle equal items.
Fact
Since ES2019 the spec requires a stable sort; every current engine (V8, SpiderMonkey, JavaScriptCore) is stable.
Myth
arr.sort(() => Math.random() - 0.5) is a fair shuffle.
Fact
It gives a biased result because sorting assumes a consistent comparator. Use the Fisher–Yates shuffle (covered in Hectal's /dsa course).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
V8 has used TimSort since version 7.0 (Chrome 70, 2018): it finds already-sorted runs and merges them, so nearly-sorted data sorts in close to O(n). Worst case is O(n log n) comparisons.
- ▸
A comparator must be consistent: if
a < bandb < cthena < c, and the same pair must always give the same sign. Inconsistent comparators make the result "implementation-defined", meaning browsers may disagree. - ▸
For sorting lots of strings in one language,
new Intl.Collator("en").compareis faster than callinglocaleCompareeach time, and supports options like{ numeric: true }, which puts"file2"before"file10". - ▸
a - bcan be wrong for values that aren't plain numbers:Infinity - InfinityisNaN, and BigInts can't mix with numbers. For dates,a - bworks becauseDateobjects convert to milliseconds; for safety, comparea.getTime() - b.getTime().
Remember this
- 1
arr.sort()reorders the array in place (it mutates the original) and returns the same array, not a copy. With no arguments it converts every element to a string and sorts by UTF-16 code units (the numbers behind each character). So[10, 1, 5, 100].sort()gives[1, 10, 100, 5], because the text"100"comes before"5". This is the default string sort trap: it's right for words in plain English, wrong for numbers. - 2
To sort any other way, pass a comparator: a function
(a, b) => numberthat compares two elements. Return a negative number ifashould come first, positive ifbshould come first, and 0 if their order doesn't matter. For numbers,(a, b) => a - bsorts ascending (small to big) and(a, b) => b - adescending. The comparator must return a number; returningtrue/false(likea > b) never gives a negative and produces wrong orders. - 3
For text, the default sort puts all capital letters before small ones (
"Zebra"before"apple") and handles accents badly.a.localeCompare(b)compares the way people expect for a language:words.sort((a, b) => a.localeCompare(b, "en")). For sorting objects, compare a property:(a, b) => a.price - b.priceor(a, b) => a.name.localeCompare(b.name). To sort by two keys, use||: compare the first key, and only if that gives 0, the second. - 4
Since ES2019,
sortis guaranteed to be stable: elements that compare equal keep their original order. That lets you sort by name first, then by grade, and students with the same grade stay in name order. Older engines didn't promise this. - 5
**
toSorted(comparator)(ES2023) returns a sorted copy** and leaves the original alone.reverse()flips the array in place; **toReversed()** (ES2023) returns a flipped copy. Before ES2023 the usual way to avoid mutation was[...arr].sort(...). Use the copy versions whenever the array came from somewhere else (function arguments, state, props). - 6
undefinedvalues always go to the end, and holes after them, without calling your comparator. Sorting is O(n log n) comparisons (V8 uses an algorithm called TimSort), so the comparator runs many times: keep it fast and consistent. If it's expensive (like parsing dates), compute the keys once first (Topic 5.4'smap), then sort.
Explain it without notes
Why does [10, 1, 5].sort() return [1, 10, 5]?
How does a comparator function work?
What does it mean that sort is stable, and why is it useful?
What is the difference between sort and toSorted?
Practice
Sort ["pear", "Fig", "apple"] alphabetically, ignoring capital letters, without changing the original.
Sort products by price (low to high), and products with the same price by name.
Trade-offs
- ↔
sortin place is cheapest in memory;toSortedallocates a copy but avoids surprising side effects. - ↔
localeComparegives correct human ordering but is slower than plain<comparisons; for big lists reuse anIntl.Collator.
Done when you can
I can explain why the default sort gets numbers wrong.
I can write comparators for ascending and descending numbers and for text.
I can sort objects by one or two properties.
I know
sortandreversemutate and can usetoSorted/toReversedinstead.I can explain what a stable sort guarantees.