- Home
- /
- Tutorials
- /
- DSA Tutorial
- /
- Sorting in JavaScript
Sorting
Sorting in JavaScript
You will almost never write a sorting algorithm at work. You will constantly write comparators - and getting one wrong is a bug that survives review because the output looks nearly right.
The default is wrong for numbers
The bug everyone hits once
javascript
const nums = [10, 9, 100, 1]
nums.sort()
console.log(nums) // [1, 10, 100, 9] — sorted as strings
nums.sort((a, b) => a - b)
console.log(nums) // [1, 9, 10, 100] — correctWithout a comparator, sort converts every element to a string. "100" comes before "9" the same way "apple" comes before "banana".
How a comparator works
Return a negative number to put a first, positive for b first, zero to leave them alone.
Comparators you will actually write
javascript
const items = [10, 2, 33, 4]
const names = ["Zoe", "ada", "Bob"]
const users = [
{ name: "Ada", age: 36 },
{ name: "Bob", age: 36 },
{ name: "Cy", age: 21 },
]
const posts = [{ published: "2024-01-02" }, { published: "2023-05-01" }]
// Numbers, ascending and descending.
items.sort((a, b) => a - b)
items.sort((a, b) => b - a)
// Strings, respecting accents and locale.
names.sort((a, b) => a.localeCompare(b))
// By a field.
users.sort((a, b) => a.age - b.age)
// By one field, then another as a tiebreak.
users.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name))
// Dates.
posts.sort((a, b) => new Date(b.published) - new Date(a.published))
console.log(items)
console.log(names)
console.log(users.map((u) => u.age))The || tiebreak works because a comparator returning 0 is falsy, so the second comparison runs only when the first ties.
sort mutates
sort changes the array in place and returns the same array. That surprises people who expect map-like behaviour.
Sorting without mutating
javascript
const original = [3, 1, 2]
const sorted = [...original].sort((a, b) => a - b)
// or, in newer runtimes:
const sorted2 = original.toSorted((a, b) => a - b)
console.log(original) // [3, 1, 2] — untouchedStability, and why it matters
A stable sort keeps equal elements in their original relative order. JavaScript's sort has been required to be stable since ES2019, and you can rely on it.
That is what makes sorting by two things possible in two passes: sort by the secondary key first, then by the primary. The stable sort preserves the first ordering within ties.
What it costs
Array.prototype.sort is O(n log n). V8 uses TimSort, which is close to O(n) on data that is already partly ordered - common in practice, and a reason not to hand-roll your own.
The comparator runs O(n log n) times, so keep it cheap. Computing a value inside a comparator is a classic slow path:
Precompute instead of recomputing
javascript
const posts = [
{ date: "2024-03-01" },
{ date: "2023-01-15" },
{ date: "2024-01-02" },
]
// Slow — parses dates on every comparison.
posts.sort((a, b) => new Date(a.date) - new Date(b.date))
// Fast — parse once, then sort by the number.
const withTime = posts.map((p) => ({ post: p, time: new Date(p.date).getTime() }))
withTime.sort((a, b) => a.time - b.time)
const sorted = withTime.map((x) => x.post)
console.log(sorted.map((p) => p.title))