Sorting puts elements in order: alphabetical names, highest scores first, earliest dates first. JavaScript's sort() is flexible, but its default behavior surprises nearly everyone the first time they sort numbers.
const names = ["Cal", "ava", "Ben", "dee"];
names.sort();
console.log(names);
By default, sort compares character codes, and every uppercase letter comes before every lowercase one. For human-friendly alphabetical order, use localeCompare:
names.sort((a, b) => a.localeCompare(b));
console.log(names);
For Z-to-A order, sort and then reverse(), which flips the order of any array:
const fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort().reverse();
console.log(fruits);
const nums = [10, 9, 1, 100, 25];
nums.sort();
console.log(nums);
The default sort turns every element into a string first, then sorts alphabetically. As text, "100" comes before "25" because "1" comes before "2", just like "apple" comes before "banana".
sort() with no arguments on numbers. Always give it a compare function.
A compare function receives two elements, a and b, and returns a number that tells sort which goes first:
| negative | a goes before b |
| positive | b goes before a |
| 0 | keep their current order |
For numbers, subtraction produces exactly that:
const nums = [10, 9, 1, 100, 25];
nums.sort((a, b) => a - b); // ascending: [1, 9, 10, 25, 100]
nums.sort((a, b) => b - a); // descending: [100, 25, 10, 9, 1]
a - b is ascending (small first). b - a is the reverse, so big first.
How it plays out: when sort compares 40 and 100, it calls the function with a = 40 and b = 100. The result, 40 - 100 = -60, is negative, so 40 goes first. sort repeats this for pairs until the whole array is in order.
Older code writes the same compare function with the function keyword. It works exactly like the arrow version:
nums.sort(function(a, b) { return a - b; });
const players = [
{ name: "Kai", score: 420 },
{ name: "Mo", score: 980 },
{ name: "Lena", score: 655 }
];
players.sort((a, b) => b.score - a.score); // high score first
console.log(players.map(p => p.name).join(", "));
players.sort((a, b) => a.name.localeCompare(b.name)); // alphabetical
console.log(players.map(p => p.name).join(", "));
Dates sort the same way as numbers, since subtracting two dates gives milliseconds: events.sort((a, b) => a.date - b.date).
You will also see string properties compared with < and >. Converting to lowercase first fixes the uppercase-first problem, and the function returns -1, 1, or 0 by hand:
const cars = [{ type: "Volvo" }, { type: "saab" }, { type: "BMW" }];
cars.sort((a, b) => {
const x = a.type.toLowerCase();
const y = b.type.toLowerCase();
if (x < y) return -1;
if (x > y) return 1;
return 0;
});
console.log(cars.map(c => c.type).join(", "));
When two elements compare as equal, sort keeps them in the order they started in. This has been guaranteed since 2019, and it means you can sort by one thing, then another:
const items = [
{ n: "A", price: 2 }, { n: "B", price: 1 },
{ n: "C", price: 2 }, { n: "D", price: 1 }
];
items.sort((a, b) => a.price - b.price);
console.log(items.map(i => i.n).join(""));
B stays ahead of D, and A ahead of C, because that is how they started.
const original = [3, 1, 2];
const sorted = original.sort((a, b) => a - b);
console.log(original); // [1, 2, 3] (changed!)
console.log(sorted === original); // true same array
To keep the original, use toSorted() (added in 2023) or sort a copy:
const original = [3, 1, 2];
const s1 = original.toSorted((a, b) => a - b); // new array
const s2 = [...original].sort((a, b) => a - b); // same idea, works everywhere
console.log(original, s1); // [3, 1, 2] [1, 2, 3]
[1, 2, 3].reverse() // [3, 2, 1] changes the original
[1, 2, 3].toReversed() // [3, 2, 1] returns a new array
If you only need the maximum or minimum, sorting the whole array is overkill:
const temps = [71, 64, 88, 79];
Math.max(...temps) // 88
Math.min(...temps) // 64
You could also sort first and take [0] and the last element, but sorting a whole array just to read two values is slow. Older code uses Math.max.apply(null, temps), which does the same job as the spread version; spread is easier to read.
Math.max(...bigArray) with 200,000 elements throws RangeError: Maximum call stack size exceeded. For very large arrays, use a plain loop.
function arrayMax(arr) {
let max = -Infinity;
for (const n of arr) {
if (n > max) max = n;
}
return max;
}
console.log(arrayMax([71, 64, 88, 79]));
Starting at -Infinity guarantees that any real number is bigger, so the first element always replaces it. This loop is also the fastest way.
You may see arr.sort(() => Math.random() - 0.5) suggested for shuffling. It looks random but is not fair: some orders come up far more often than others. Use the Fisher-Yates shuffle instead:
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = a[i]; // swap a[i] and a[j]
a[i] = a[j];
a[j] = temp;
}
return a;
}
console.log(shuffle(["A", "B", "C", "D"])); // a different order each run
What does this log?
console.log(["b", "a", "C"].sort());
["C", "a", "b"]. The default sort compares character codes, and every uppercase letter comes before every lowercase one.A compare function returns a negative number for (a, b). What happens?
b goes before aa goes before ba first, positive means b first, and 0 keeps their current order.What does this log?
const a = [3, 1, 2];
const b = a.toSorted((x, y) => x - y);
console.log(a[0], b[0]);
3 1. toSorted returns a sorted copy and leaves a alone. With sort, both would print 1.sort() then with localeCompareshuffle() function into the Playground and shuffle a deck of 10 numbers several times