Skip to content
ByteStepStart with recursion

Sorting: why O(n log n) beats O(n²)

Free, no account

Sorting is the standard place to meet the difference between O(n²) and O(n log n), because you can watch both happen to the same array and count what each one costs.

Below, 14 values starting in the same order every time. Bubble sort settles them in 91 comparisons. Merge sort settles the same 14 values in 31. Nothing is estimated: each comparison is counted as it happens.

Watch bubble first, and notice how much of its work is moving a value one place at a time. Then merge, which never compares most pairs at all, because it only ever merges runs that are already in order.

1function bubble(a) {
2 for (let end = a.length - 1; end > 0; end--) {
3 for (let i = 0; i < end; i++) {
4 if (a[i] > a[i + 1]) swap(a, i, i + 1)
5 }
6 }
7}

The array

orange is being compared, teal is settled

bubble sort · O(n²)

  1. 1
  2. 3
  3. 5
  4. 7
  5. 9
  6. 10
  7. 8
  8. 6
  9. 4
  10. 2

0 comparisons · 0 swaps

10 values, in no particular order, and bubble sort is about to put them straight. Watch the comparison count more than the array.

1/76
Detail
76 steps

Why the quadratic sorts are quadratic, and what merge sort does instead.

Why the quadratic sorts are quadratic

Bubble sort makes a pass, comparing every neighbouring pair, and after each pass the largest remaining value has reached the end. That is one value placed per pass, and a pass costs as many comparisons as there are items left.

So the work is n plus n minus one plus n minus two, all the way down, which is about n²/2. At 14 items that is exactly 91 comparisons, and the animation counts every one of them.

Insertion sort looks different and costs the same in the worst case. It takes each value and walks it backwards into place, which is again a pass per item. Its advantage is elsewhere, and worth knowing: on input that is already nearly sorted, each value walks almost nowhere and the whole thing behaves linearly.

What merge sort does instead

Merge sort never compares most pairs. It sorts runs of one, which are sorted by definition, then merges neighbouring runs into longer sorted runs, then merges those. Each round doubles the run length, so the number of rounds is the number of times the array can be halved.

Merging two sorted runs is the cheap part. Because both sides are already in order, you only ever compare their two front values, take the smaller, and move on. Every item is looked at once per round.

One pass over everything, log n times. That product is the shape, and the reason it beats a quadratic sort by more and more as the array grows rather than by a constant amount.

The counts, at the same size

On 14 items: 91 comparisons for bubble sort, 55 for insertion sort, 31 for merge sort. Drag the slider and watch the first two climb far faster than the third.

Know someone stuck on this? Send it to them.

A classmate, a study group, someone learning this on their own at midnight. The link opens the lesson set up exactly as you have it, the same function and the same number, and runs from the first step so they watch the whole thing build rather than landing in the middle of it.

Also in this course

Big-O: what “grows faster” actually means

Where the two shapes in this lesson come from, measured rather than drawn.

Start there →