← Explainer Library

Interactive Explainer

Sorting, Race by Race

Five classic sorts on the same array, stepping in lockstep. Comparisons and writes count up live. Toggle the input distribution to watch quicksort's worst case appear and insertion sort win on tiny arrays. The asymptotics are equal between three of them — the constants are not.

Prelude

The asymptotic answer hides the real one

Every algorithms textbook lists the same table: mergesort, quicksort, heapsort — all $O(n \log n)$. Insertion sort, bubble sort, selection sort — all $O(n^2)$. End of story. Yet your standard library's sort is none of those three $n \log n$ algorithms. It's Timsort (Python, Java) or pdqsort (Rust, C++). Both spend a surprising amount of code calling insertion sort.

Asymptotics tell you how the cost scales. They don't tell you what the cost is. On 16 elements, insertion sort is faster than quicksort. On a nearly-sorted array of a million, Timsort beats vanilla quicksort by 10× by detecting existing runs. On adversarial input, a naïve quicksort degrades to $O(n^2)$ and a balanced mergesort doesn't.

The race below shows all of this directly. Five algorithms start on the same array and step one operation at a time. You can pick the input distribution, count comparisons and writes, and see the constants the big-O notation discards.

Step 1

The race

Each card shows an algorithm's current state: bars are values, the highlighted bar is the position currently being compared, swaps flash, sorted segments turn teal. All cards advance one operation per tick.

unsorted bar comparing writing pivot / aux final position

Things worth trying:

Step 2

Insertion sort — small-n champion

Walk through the array. For each new element, slide it leftward past every larger element until it lands.

Two facts that make insertion sort matter beyond textbook examples:

Step 3

Mergesort — the deterministic n log n

Split the array in half, sort each half recursively, merge the two sorted halves in linear time. The merge is the heart of it:

Mergesort's guarantees:

Step 4

Quicksort — and the pivot question

Pick a pivot. Partition the array into "less than pivot", "equal", "greater than pivot". Recurse on the two outer partitions. The maths is similar to mergesort — a divide-and-conquer recursion that costs $O(n \log n)$ on average — but the implementation has one painful decision: which pivot?

Pivot strategies, ranked from "easy to break" to "production":

Why quicksort wins on real data despite a worse worst case. Cache locality. Each partition step touches contiguous memory; recursion descends into smaller and smaller contiguous ranges that fit in L1 / L2 cache. Heapsort and mergesort have access patterns that thrash the cache. Asymptotics don't see this — your CPU does.
Step 5

Heapsort — guaranteed n log n, in place

Build a max-heap from the array in $O(n)$ (Floyd's bottom-up heapify — see the Trees article). Then repeatedly extract-max into the last slot of the array, shrinking the heap by one. The extracted maxes accumulate in sorted order at the tail.

The combination is rare and useful: $O(n \log n)$ worst case, in place, no auxiliary memory. Why isn't it the standard? Cache. The implicit binary tree's array indices $i, 2i+1, 2i+2$ scatter accesses across memory; each sift-down is a series of cache misses. Quicksort touches contiguous regions; heapsort jumps. On real hardware quicksort wins by 2–3× for the same $n$.

Step 6

The big-O table — and what it leaves out

algorithm best average worst memory stable in place adaptive
insertion $O(n)$ $O(n^2)$ $O(n^2)$ $O(1)$ yes yes yes
bubble $O(n)$ $O(n^2)$ $O(n^2)$ $O(1)$ yes yes yes
merge $O(n \log n)$ $O(n \log n)$ $O(n \log n)$ $O(n)$ yes no no
quick (random pivot) $O(n \log n)$ $O(n \log n)$ $O(n^2)$ $O(\log n)$ stack no yes no
heap $O(n \log n)$ $O(n \log n)$ $O(n \log n)$ $O(1)$ no yes no
Timsort $O(n)$ $O(n \log n)$ $O(n \log n)$ $O(n)$ yes no yes
introsort (pdqsort) $O(n \log n)$ $O(n \log n)$ $O(n \log n)$ $O(\log n)$ stack no yes partial

The columns that the textbook table omits — stable, in place, adaptive — are exactly the ones that decide which sort your standard library actually ships.

Step 7

Beyond the comparison-sort ceiling

Any comparison-based sort must take at least $\Omega(n \log n)$ comparisons in the worst case — every permutation needs to be distinguishable, and a comparison tree of depth $d$ can only distinguish $2^d$ permutations.

If you don't need to compare — i.e., your keys are structured — you can do better.

One rule of thumb. For general-purpose ordering of comparable objects, use your language's built-in sort — it is Timsort or pdqsort and has been carefully tuned. Reach for a custom sort only when (a) you need a guarantee the standard sort doesn't give (worst-case $O(n \log n)$? stable across primitive and object?) or (b) your keys are structured enough for counting / radix / bucket to actually win.
Step 8

Reading list