Quicksort: Sorting by Randomization
Quicksort is the "aggressive" cousin of Mergesort. While Mergesort carefully splits first and does the hard work during the merge, Quicksort does the hard work upfront by partitioning and then simply lets recursion handle the rest.
Core idea: Pick an element (the pivot). Rearrange the array so everything smaller than the pivot is on the left and everything larger is on the right. Repeat for both sides.
How It Actually Works
Let's trace sorting [2, 8, 7, 1, 3, 5, 6, 4] all the way down to the final sorted array.
Pass 1 — Full Array, Pivot = 4
4We use two pointers: i (the boundary for small elements) and j (our scanner).
jfinds2:2 < 4→ swap2with itself, advancei.jfinds8:8 > 4→ do nothing.jfinds7:7 > 4→ do nothing.jfinds1:1 < 4→ swap1with8, advancei.jfinds3:3 < 4→ swap3with7, advancei.jfinds5:5 > 4→ do nothing.jfinds6:6 > 4→ do nothing.End of scan → swap pivot
4into the boundary position.
Result:
4 is now in its permanent sorted position at index 3.
Pass 2 — Left subarray [2, 1, 3], Pivot = 3
[2, 1, 3], Pivot = 3jfinds2:2 < 3→ swap2with itself, advancei.jfinds1:1 < 3→ swap1with itself, advancei.End of scan → swap pivot
3into the boundary position.
Result:
3 is now in its permanent sorted position.
Pass 3 — Left subarray [2, 1], Pivot = 1
[2, 1], Pivot = 1jfinds2:2 > 1→ do nothing.End of scan → swap pivot
1into the boundary position (index 0).
Result:
Both 1 and 2 are now in their permanent sorted positions (a single-element subarray is trivially sorted).
Left side is fully sorted: [1, 2, 3, 4, ...]
Pass 4 — Right subarray [5, 6, 8, 7], Pivot = 7
[5, 6, 8, 7], Pivot = 7jfinds5:5 < 7→ swap5with itself, advancei.jfinds6:6 < 7→ swap6with itself, advancei.jfinds8:8 > 7→ do nothing.End of scan → swap pivot
7into the boundary position.
Result:
7 and 8 are now in their permanent sorted positions.
Pass 5 — Left subarray [5, 6], Pivot = 6
[5, 6], Pivot = 6jfinds5:5 < 6→ swap5with itself, advancei.End of scan → swap pivot
6into the boundary position.
Result:
Both 5 and 6 are now in their permanent sorted positions.
Final Result
Assembling all the pieces:
Why the Partition Works
The magic of Quicksort is that the partition step achieves two goals at once:
The pivot is placed in its final resting place.
The array is segregated: no element on the left will ever need to swap with an element on the right.
Unlike Mergesort, which requires an extra array (O(n) space) to merge, Quicksort swaps elements in-place.
Understanding the Complexity
The Best Case: O(nlogn)
If our pivot always lands near the middle, we halve the problem size every time (just like Mergesort).
Levels: log2n
Work per level: O(n) to scan and swap.
Total: O(nlogn)
The Worst Case: O(n2)
If we pick a terrible pivot (like the smallest or largest element every time), we only reduce the problem size by one element per level.
Levels: n
Work per level: O(n)
Total: O(n2)
This worst case occurs when the array is already sorted and we always pick the last element as the pivot!
The Solution: Randomization
To avoid the O(n2) trap, we don't just pick the last element. We pick a random element and swap it to the end to be our pivot. This makes it statistically impossible to hit the worst case on any input.
Implementation (Standard Partition)
Summary
Worst case
O(n2) (rare with randomization)
Average case
Θ(nlogn)
Space
O(logn) (stack space)
Stable?
No
Best for
General purpose sorting, in-memory arrays
Quicksort is usually 2–3× faster than Mergesort in practice because its inner loop is incredibly simple and it plays nicely with CPU caches (it doesn't jump between different arrays).
Last updated