Quicksort

Partition an array in place, choose a pivot that survives real input, and see the O(n²) that the textbook version really does hit.

10of 24
≈ 14Min read
5Parts

Partition, then recurse

Merge sort does its work on the way back up, in the merge. Quicksort does its work on the way down, in the partition — and that is why it needs no merge step and no extra array.

After a partition the pivot is exactly where it belongs in the finished array, with everything smaller to its left and everything larger to its right. Neither side ever has to look at the other again.

js
function quickSort(a, lo = 0, hi = a.length - 1) {
  if (lo >= hi) return a;

  const p = partition(a, lo, hi);     // the pivot is now in its FINAL place
  quickSort(a, lo, p - 1);
  quickSort(a, p + 1, hi);

  return a;
}

Lomuto partition

One pass, one boundary index, O(1) extra memory. It is the easiest partition to get right, and the one to learn first. Hoare’s two-pointer version does fewer swaps and is what production code uses, but it does not leave the pivot in its final position, which makes it harder to reason about.

js
function partition(a, lo, hi) {
  const pivot = a[hi];               // the last element, for now
  let i = lo;                        // the boundary: everything before i is < pivot

  for (let j = lo; j < hi; j++) {
    if (a[j] < pivot) { [a[i], a[j]] = [a[j], a[i]]; i++; }
  }

  [a[i], a[hi]] = [a[hi], a[i]];     // put the pivot at the boundary
  return i;
}

The pivot decides everything

The worst case is not exotic. Taking the last element as the pivot makes already-sorted input the worst case, and already-sorted input is extremely common — which is how this becomes a production incident rather than an exam question.

Median-of-three is the usual fix: cheap, deterministic, and it makes sorted and reverse-sorted input the best case instead of the worst.

Pivot choiceOn random inputOn already-sorted input
Last elementn log nn² — every partition is empty on one side
Random elementn log nn log n, with overwhelming probability
Median of first, middle, lastn log nn log n, and no extra randomness needed
True median (medians of medians)n log n guaranteedn log n, with a constant nobody wants

Against merge sort

Quicksort is usually the faster of the two in practice because it moves data within one array and the processor loves that. Its two weaknesses — the quadratic worst case and the lack of stability — are exactly why real libraries do not ship it plain.

QuicksortMerge sort
Averagen log n, small constantn log n, larger constant
Worstn²n log n
Extra memoryO(log n) stackO(n)
StableNoYes
Cache behaviourExcellent — partitions in placeGood

What a real library actually runs

  • Introsort (C++ std::sort): quicksort, but it counts its own recursion depth and switches to heapsort past 2·log n. Quicksort speed, with an n log n guarantee.
  • Timsort (Python, Java objects, V8): merge sort that finds runs already in order and uses insertion sort on short ones. Stable, and O(n) on nearly-sorted data.
  • Insertion sort at the bottom: every one of them stops recursing at about ten items and finishes with insertion sort.
  • Three-way partition when duplicates are likely: < = > rather than < >, so a million copies of the same value sort in one pass instead of quadratically.

Practice