Visual walkthrough — Quick sort — Lomuto - Hoare partition, pivot strategies, expected O(n log n), worst case
Step 1 — What "one partition" actually costs
WHAT. Take an array segment. Pick one element — the pivot, the value we compare everything against. Partitioning means: walk across the whole segment once, comparing each element to the pivot and pushing smaller ones left, larger ones right.
WHY. Before we can count the total work, we must know the cost of the smallest unit of work — a single partition. Everything else is just many partitions stacked in a tree.
PICTURE. Below, the segment has boxes. The scan pointer (orange) visits every box exactly once. That is comparisons — one arrow per box. So:
The symbol is just "the time for one compare-and-maybe-swap"; is the segment length. Read it as: partition cost grows in a straight line with segment size.
Step 2 — Stacking partitions into a tree
WHAT. After one partition the pivot is fixed, and we are left with two smaller segments — the "less" side and the "greater" side. Each becomes a new partition problem. Draw a node for every partition; connect a parent to its two children.
WHY. The total cost of quicksort is the sum of all partition costs across all nodes. If we can add up the costs level-by-level, we can find the total. So we need the shape of this tree.
PICTURE. The root handles all boxes. Its two children together also cover (almost) all boxes — the pivot dropped out, but everything else is redistributed. So each full level of the tree costs about : the same elements, just chopped into more pieces.
The total is therefore:
Everything now hinges on one question: how tall is the tree? That is the whole game.
Step 3 — The dream split: exactly in half
WHAT. Suppose every pivot lands dead centre, so each segment of size becomes two of size .
WHY. This is the best possible shape. If we understand it, we have a floor to compare everything else against.
PICTURE. Sizes go . How many halvings until we reach a single box? That is exactly the question ==== answers: "how many times do I halve to reach ?"
Why and not, say, subtraction? Because at each step we divide the size, we don't subtract a fixed chunk. Repeated division is what a logarithm counts. Plugging the height into Step 2:
Term by term: is the cost of one level (Step 2); is the number of levels (this step). Multiply → the famous .
Step 4 — You don't need exact halves
WHAT. Real pivots are rarely perfect. Suppose every split is a lopsided vs instead of vs .
WHY. If quicksort only worked on perfect halves it would be useless — random pivots never split perfectly. We must show that any constant fraction is good enough.
PICTURE. Follow the longest branch (always the side). Sizes go . The chain ends when , giving:
The base of the log changed from to — but a change of log base is only a constant multiplier, so the height is still . The picture shows the two branch lengths: the short branch dies fast; even the long branch reaches bottom in logarithmically many steps.
Key visual takeaway: as long as the split ratio doesn't drift toward "0 vs everything," the tree stays short. This is the buffer that makes random pivots safe.
Step 5 — Averaging over random pivots: the "gap" trick
WHAT. Instead of guessing a split ratio, average over all pivot choices. Line the elements up in sorted order and name them (just labels for "the smallest," "the 2nd smallest," …). Ask: do and ever get compared?
WHY. Total comparisons = total work. Counting comparisons directly is a nightmare of nested recursion. But if we ask the yes/no question "are these two ever compared?" for every pair, we can add up tiny probabilities instead. This is the indicator variable trick.
PICTURE. Consider the gap — all sorted values between the two, inclusive. Whichever of these is chosen as a pivot first decides their fate:
- If the first pivot from the gap is or → they are compared (pivot vs the other, directly).
- If it is anyone strictly between → that pivot splits them into different subarrays, and they never meet again.
So the pair is compared only when the winner of the gap is one of the two endpoints: good choices out of equally likely first-picks.
= the two endpoints; = how many elements sit in the gap (each equally likely to be picked first, since the pivot is random).
Step 6 — Summing the tiny probabilities into
WHAT. Add the per-pair probabilities over every pair to get the expected total comparisons.
WHY. Expectation of a sum = sum of expectations (this holds even when events overlap — the magic of linearity). So the messy dependent recursion becomes a plain double sum.
PICTURE. Group pairs by their gap width . For a fixed left endpoint , the gap widths run , and each contributes . The picture stacks these shrinking bars — they are exactly a harmonic series.
- = over all pairs of elements.
- = each pair's compare-probability from Step 5.
- = the -th harmonic number, the total height of all those shrinking bars.
The bars of hug the curve , and the area under from to is . That is why :
The constant comes from converting natural log to base-2 log (, times the ... shown exactly in the verify block).
Step 7 — The degenerate case: worst-possible pivots
WHAT. Now break it on purpose. Suppose every pivot is the smallest remaining element (this happens with last-element pivot on an already-sorted array).
WHY. Contract rule: cover the limiting case. The reader must see why the same algorithm collapses to .
PICTURE. A perfect pivot removes the middle and splits in half. The worst pivot removes only itself — one endpoint — leaving a segment of size . The tree is no longer bushy; it's a single dangling chain of height . The level costs shrink only by each time:
- = the lone child, only one element smaller.
- = we still scanned all boxes to peel off one.
- The sum is the classic triangle — quadratic.
Compare the two trees side by side: the balanced tree is short and wide ( tall); the degenerate tree is tall and thin ( tall). Same per-level idea, disastrously different height. This is exactly the case Introsort escapes by bailing out to Heap Sort once the recursion gets too deep, and random pivots make astronomically unlikely.
The one-picture summary
Everything on one canvas: partition cost is a straight line ( per level, Step 1–2); the height is decided by the pivot. Good/lucky pivots → short tree → (Steps 3–6). Adversarial pivots → tall chain → (Step 7). The "gap" probability is the bridge that turns "average over random pivots" into the clean harmonic sum .
Recall Feynman retelling — say it out loud in plain words
Picture sorting as chopping a line of numbers. You point at one number — the pivot — and shove everything smaller to its left, bigger to its right. That one sweep costs one look per number, so it's a straight-line cost, call it .
Now you've got two smaller lines. Do the same to each. Draw this as a family tree of chops. Every full row of the tree touches all numbers once, so every row costs about . The only thing left to figure out is: how many rows tall is the tree?
If your pivot always lands near the middle, each line halves, and halving down to takes only steps — a short, fat tree — so you pay . Even a sloppy 10-90 split just changes the log's base, which is a harmless constant, so you're still fine.
To handle random pivots without guessing a ratio, use the gap trick: sort the values in your head as . Two of them, and , only ever get compared if the first pivot picked from the block between them happens to be one of those two endpoints — that's winners out of candidates. Add that little over every pair and you get , and is just , which piles up to about . So the average is .
The disaster case: if the pivot is always the tiniest element, each chop peels off just one number, so the tree is a long skinny chain rows deep, and per row gives . Fix it by picking pivots at random or bailing to heapsort when the tree gets too deep.
Recall Quick self-check
Why does each level of the recursion tree cost about ? ::: Because the elements are just redistributed among that level's segments — every element (bar the pivots) appears exactly once per level, and partitioning scans each once. Why is even a 1:9 split still ? ::: The longest branch shrinks by a constant factor each step, so height , which differs from only by a constant. Height stays . When are and compared? ::: Exactly when the first pivot chosen from is or ; probability . What makes the tree tall in the worst case? ::: A pivot that is always the min (or max) peels off only one element, so each segment shrinks by , not by half.
See also: Merge Sort (splits trivially, works while merging — the mirror image), Quickselect (same partition, recurse on one side, expected ), Dutch National Flag Problem (3-way split for duplicates), Binary Search (the other place height shows up).