Episode 1 — Fundamentals / 1.22 — Array Methods
1.22.g — sort()
In one sentence:
sort()re-orders the array in place (mutating it), and its default behavior converts elements to strings for lexicographic comparison — which produces surprising results for numbers.
Navigation: ← 1.22.f — slice() and splice() · 1.22.h — flat() and flatMap() →
1. sort() basics
sort() sorts the elements of an array in place and returns the same array (not a copy).
const fruits = ["banana", "apple", "cherry"];
const result = fruits.sort();
console.log(fruits); // ["apple", "banana", "cherry"] ← mutated!
console.log(result === fruits); // true ← same reference
2. Default sort is LEXICOGRAPHIC
This is the single most important gotcha with sort:
const nums = [10, 2, 1, 20, 3, 100];
nums.sort();
console.log(nums); // [1, 10, 100, 2, 20, 3] ← NOT numeric order!
Why? Without a compare function, sort converts every element to a string and compares by Unicode code points:
"1" < "10" < "100" < "2" < "20" < "3"
The string "10" comes before "2" because "1" (char code 49) < "2" (char code 50).
// More surprising examples
[80, 9, 700, 40, 1, 5, 200].sort();
// [1, 200, 40, 5, 700, 80, 9]
[true, false, true].sort();
// [false, true, true] — "false" < "true" lexicographically
3. The compare function
To sort correctly, provide a compare function:
arr.sort((a, b) => {
// return negative → a comes first
// return 0 → keep original order
// return positive → b comes first
});
| Return value | Order |
|---|---|
< 0 | a before b |
0 | Keep original relative order |
> 0 | b before a |
4. Ascending numbers: (a, b) => a - b
const nums = [10, 2, 1, 20, 3, 100];
nums.sort((a, b) => a - b);
console.log(nums); // [1, 2, 3, 10, 20, 100] ← correct!
How it works:
a - bis negative whena < b→astays firsta - bis 0 whena === b→ order preserveda - bis positive whena > b→bmoves first
5. Descending numbers: (a, b) => b - a
const nums = [10, 2, 1, 20, 3, 100];
nums.sort((a, b) => b - a);
console.log(nums); // [100, 20, 10, 3, 2, 1]
6. Sorting strings
Default sort() works for simple ASCII strings:
const names = ["Charlie", "Alice", "Bob"];
names.sort();
console.log(names); // ["Alice", "Bob", "Charlie"]
Case sensitivity problem
const words = ["banana", "Apple", "cherry"];
words.sort();
console.log(words); // ["Apple", "banana", "cherry"]
// Uppercase letters have lower code points than lowercase
// "A" (65) < "b" (98) — so "Apple" comes before "banana"
localeCompare for proper string sorting
const words = ["banana", "Apple", "cherry"];
words.sort((a, b) => a.localeCompare(b));
console.log(words); // ["Apple", "banana", "cherry"]
// Case-insensitive sorting
words.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
console.log(words); // ["Apple", "banana", "cherry"]
Internationalization
const german = ["Zahlen", "Apfel", "Ubung"];
german.sort((a, b) => a.localeCompare(b, "de"));
// Sorted correctly for German locale
7. Sorting objects by property
By number property
const students = [
{ name: "Alice", grade: 85 },
{ name: "Bob", grade: 92 },
{ name: "Carol", grade: 78 },
{ name: "Dave", grade: 96 },
];
// Sort by grade ascending
students.sort((a, b) => a.grade - b.grade);
console.log(students.map(s => `${s.name}: ${s.grade}`));
// ["Carol: 78", "Alice: 85", "Bob: 92", "Dave: 96"]
// Sort by grade descending
students.sort((a, b) => b.grade - a.grade);
console.log(students.map(s => `${s.name}: ${s.grade}`));
// ["Dave: 96", "Bob: 92", "Alice: 85", "Carol: 78"]
By string property
const users = [
{ name: "Charlie", age: 30 },
{ name: "Alice", age: 25 },
{ name: "Bob", age: 35 },
];
users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users.map(u => u.name));
// ["Alice", "Bob", "Charlie"]
Multi-level sort (sort by age, then by name)
const people = [
{ name: "Bob", age: 25 },
{ name: "Alice", age: 25 },
{ name: "Charlie", age: 30 },
{ name: "Dave", age: 25 },
];
people.sort((a, b) => {
// Primary: sort by age ascending
if (a.age !== b.age) return a.age - b.age;
// Secondary: sort by name alphabetically
return a.name.localeCompare(b.name);
});
console.log(people.map(p => `${p.name} (${p.age})`));
// ["Alice (25)", "Bob (25)", "Dave (25)", "Charlie (30)"]
8. Stable sort (guaranteed since ES2019)
A stable sort preserves the relative order of elements that compare as equal. Since ES2019, Array.prototype.sort is guaranteed to be stable.
const items = [
{ name: "A", priority: 1 },
{ name: "B", priority: 2 },
{ name: "C", priority: 1 },
{ name: "D", priority: 2 },
];
items.sort((a, b) => a.priority - b.priority);
// Stable: A stays before C (both priority 1), B stays before D (both priority 2)
// [A(1), C(1), B(2), D(2)]
Before ES2019, some engines used unstable algorithms (like quicksort). Now you can rely on stability.
9. toSorted() (ES2023) — non-mutating version
toSorted works exactly like sort but returns a new array, leaving the original untouched:
const nums = [3, 1, 4, 1, 5, 9];
const sorted = nums.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5, 9]
console.log(nums); // [3, 1, 4, 1, 5, 9] ← unchanged!
This is the preferred approach in modern code, especially in React and functional programming where you should not mutate state.
// Other non-mutating array methods (ES2023):
// toSorted() — non-mutating sort
// toReversed() — non-mutating reverse
// toSpliced() — non-mutating splice
// with(index, value) — non-mutating index assignment
Workaround for older environments
// Before toSorted existed:
const sorted = [...nums].sort((a, b) => a - b);
// or
const sorted2 = nums.slice().sort((a, b) => a - b);
10. sort() mutates — beware!
function getTopStudents(students) {
// BUG: this mutates the original array!
return students.sort((a, b) => b.grade - a.grade).slice(0, 3);
}
const allStudents = [
{ name: "Alice", grade: 78 },
{ name: "Bob", grade: 92 },
{ name: "Carol", grade: 85 },
];
const top = getTopStudents(allStudents);
console.log(allStudents);
// Now allStudents is sorted too! Not what the caller expected.
// FIX: copy first
function getTopStudentsSafe(students) {
return [...students].sort((a, b) => b.grade - a.grade).slice(0, 3);
}
11. Real-world examples
Leaderboard
const players = [
{ name: "Alice", score: 2400 },
{ name: "Bob", score: 3100 },
{ name: "Carol", score: 2800 },
{ name: "Dave", score: 3100 },
];
const leaderboard = [...players]
.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score; // desc by score
return a.name.localeCompare(b.name); // asc by name (tiebreaker)
})
.map((p, i) => `${i + 1}. ${p.name} — ${p.score}`);
console.log(leaderboard);
// ["1. Bob — 3100", "2. Dave — 3100", "3. Carol — 2800", "4. Alice — 2400"]
Price sorting (e-commerce)
const products = [
{ name: "Laptop", price: 999 },
{ name: "Phone", price: 699 },
{ name: "Tablet", price: 499 },
{ name: "Watch", price: 299 },
];
// Sort by price: low to high
const lowToHigh = [...products].sort((a, b) => a.price - b.price);
// Sort by price: high to low
const highToLow = [...products].sort((a, b) => b.price - a.price);
Sort by date
const events = [
{ name: "Meeting", date: "2024-03-15" },
{ name: "Lunch", date: "2024-03-10" },
{ name: "Workshop", date: "2024-03-20" },
];
events.sort((a, b) => new Date(a.date) - new Date(b.date));
console.log(events.map(e => e.name)); // ["Lunch", "Meeting", "Workshop"]
Sort by boolean (true first)
const tasks = [
{ text: "Task A", urgent: false },
{ text: "Task B", urgent: true },
{ text: "Task C", urgent: false },
{ text: "Task D", urgent: true },
];
tasks.sort((a, b) => Number(b.urgent) - Number(a.urgent));
// Urgent tasks first: [B, D, A, C]
12. Sorting gotchas summary
| Gotcha | Example | Fix |
|---|---|---|
| Default is lexicographic | [10, 2].sort() → [10, 2] | sort((a, b) => a - b) |
| Mutates original | arr.sort() changes arr | [...arr].sort() or toSorted() |
| Uppercase before lowercase | ["b", "A"].sort() → ["A", "b"] | sort((a, b) => a.localeCompare(b)) |
| Returns same array | sorted === original is true | Be aware, not a bug |
Key takeaways
sort()sorts in place — it mutates the original array and returns the same reference.- Default sort is lexicographic (string comparison) — always pass a compare function for numbers.
(a, b) => a - bfor ascending,(a, b) => b - afor descending.- Use
localeComparefor proper string and international sorting. toSorted()(ES2023) is the non-mutating alternative — prefer it in modern code.- Copy before sorting (
[...arr].sort(...)) if you need to preserve the original.
Explain-It Challenge
Explain without notes:
- Why does
[10, 2, 1].sort()produce[1, 10, 2]? - Write a compare function to sort an array of objects by
pricein descending order. - How does
toSorteddiffer fromsort, and why would you prefer it?
Navigation: ← 1.22.f — slice() and splice() · 1.22.h — flat() and flatMap() →