Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~19 min· topic 6 of 10

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".

app.jswhole filejavascript
[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.

app.jswhole filejavascript
[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.

Understand what the comparator answersdiagram
Rendering diagram…

04Sort objects by a property

Compare the property you care about. For text, use localeCompare.

app.jswhole filejavascript
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.

app.jswhole filejavascript
const ranked = scores.toSorted((a, b) => b - a);
const newestFirst = posts.toReversed();

Try it yourself

  1. 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 changing prices. Predict the array.

  2. 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. 3

    Watch the comparator

    Add console.log(a, b) inside (a, b) => a - b in the first example. Predict how many times it runs for 5 numbers (it's more than 4), then count.

Code & diagrams

The default string sort trap New tab

`[...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 mutates; toSorted and toReversed copy ES2023+ New tab

`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 ]
Sort a table of objects (stable, two keys) ES2023+ New tab

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

Press Run to see the page.

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].

terminal
$ Console
── what you'll see ──
[ 100, 80, 9 ]

Break #2

Returning a boolean from the comparator

Write nums.sort((a, b) => a > b).

terminal
$ the page shows
── what you'll see ──
The order is wrong or unchanged, and may differ between browsers and array sizes.

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.

terminal
$ the page shows
── what you'll see ──
Your main list is now in a different order everywhere else on the page.

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 < b and b < c then a < 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").compare is faster than calling localeCompare each time, and supports options like { numeric: true }, which puts "file2" before "file10".

  • ▸

    a - b can be wrong for values that aren't plain numbers: Infinity - Infinity is NaN, and BigInts can't mix with numbers. For dates, a - b works because Date objects convert to milliseconds; for safety, compare a.getTime() - b.getTime().

Remember this

  1. 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. 2

    To sort any other way, pass a comparator: a function (a, b) => number that compares two elements. Return a negative number if a should come first, positive if b should come first, and 0 if their order doesn't matter. For numbers, (a, b) => a - b sorts ascending (small to big) and (a, b) => b - a descending. The comparator must return a number; returning true/false (like a > b) never gives a negative and produces wrong orders.

  3. 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.price or (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. 4

    Since ES2019, sort is 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. 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. 6

    undefined values 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's map), then sort.

Explain it without notes

01

Why does [10, 1, 5].sort() return [1, 10, 5]?

02

How does a comparator function work?

03

What does it mean that sort is stable, and why is it useful?

04

What is the difference between sort and toSorted?

Practice

01

Sort ["pear", "Fig", "apple"] alphabetically, ignoring capital letters, without changing the original.

02

Sort products by price (low to high), and products with the same price by name.

Trade-offs

  • ↔

    sort in place is cheapest in memory; toSorted allocates a copy but avoids surprising side effects.

  • ↔

    localeCompare gives correct human ordering but is slower than plain < comparisons; for big lists reuse an Intl.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 sort and reverse mutate and can use toSorted/toReversed instead.

  • I can explain what a stable sort guarantees.