Quicksort picks the last element as a pivot, sweeps the range once, and moves everything smaller to the left until the pivot lands in its final position. The two sides are then sorted independently. The partition is linear, so the recursion depth decides everything: balanced splits give O(n log n), while a last-element pivot on sorted or reversed input splits off one element at a time and degrades to O(n²).
The partition invariant
- During the sweep, everything in a[lo..i−1] is ≤ pivot and everything in a[i..j−1] is > pivot.
- The index i is the boundary where the next small element will be placed.
- When j reaches hi the invariant has consumed the whole range except the pivot itself.
The pivot lands in place
- The last element is marked as the pivot before the sweep.
- After the sweep it is swapped into position i, which is its final index.
- Only then are the left and right subranges sorted, independently of each other.
Counting comparisons
- Each partition compares every other element of its range against the pivot exactly once.
- Random input averages ≈1.39nlog2n comparisons: the constant is worse than merge sort's, which is why the in-place partition still competes.
- Every element is compared once per partition it belongs to, so the total is the sum of the range sizes minus one per partition.
Worst case: already ordered
- With the last element as pivot, sorted or reversed input keeps splitting off a single element.
- The ranges shrink by one instead of halving, so comparisons reach 2n(n−1)=O(n2).
- A random or median-of-three pivot fixes this in practice; the recursion depth is the price of the in-place partition.
Duplicate keys
- Equal keys satisfy the ≤ test, so they all move to the left partition.
- A run of equal keys is therefore not split in half — Lomuto's scheme degrades on duplicate-heavy input.
- Three-way partitioning (Dutch national flag) is the standard fix when duplicates are common.