Partitioning
Rearrange an array in place so that elements less than, equal to, and greater than a pivot occupy contiguous regions, using pointers that mark region boundaries.
Overview
Partitioning is the workhorse inside Quick Sort and Quickselect, and a complete solution on its own for problems like Sort Colors. Given a pivot value, one linear pass rearranges the array so every element < pivot precedes every element ≥ pivot (two-way), or so the three groups < pivot, == pivot, > pivot are contiguous (three-way, the Dutch national flag problem posed by Dijkstra).
Three schemes are standard. Lomuto uses a single boundary pointer and is the easiest to get right. Hoare uses two converging pointers and does roughly three times fewer swaps on average. Dutch national flag uses three regions and handles many equal keys, which is where two-way schemes degrade to O(n²) in quicksort.
Intuition
A mental model before the formal terms.
You are sorting a shelf of books into "short" and "tall" with a single divider. Lomuto: walk left to right; each time you meet a short book, swap it into the slot right after the divider and slide the divider one to the right. Everything left of the divider is short, everything between the divider and your hand is tall, everything ahead is unsorted.
Dutch national flag with red/white/blue balls: keep a red region growing from the left, a blue region growing from the right, and examine balls in the middle. A red ball goes left, a blue ball goes right, a white ball stays. Three fences, one pass.
How it works
- Lomuto (pivot at
a[hi]):i = lomarks the end of the< pivotregion. Forjfromlotohi - 1, ifa[j] < pivotswapa[i]anda[j], theni++. Finally swapa[i]witha[hi]; the pivot is now at indexiand in its final sorted position. - Hoare (pivot value
p = a[lo]):i = lo - 1,j = hi + 1. Repeat: advanceiuntila[i] ≥ p, retreatjuntila[j] ≤ p; ifi < jswap, else returnj. Everything in[lo, j]is≤ pand everything in[j+1, hi]is≥ p; the pivot is *not* necessarily atj. - Dutch national flag (values
0/1/2, or<,==,>relative to a pivot):low = 0,mid = 0,high = n - 1. Whilemid ≤ high: ifa[mid] == 0swapa[low], a[mid],low++,mid++; ifa[mid] == 1mid++; ifa[mid] == 2swapa[mid], a[high],high--(do not advancemid— the swapped-in element is unexamined).
Why it works
Lomuto invariant: at the top of each iteration, a[lo..i) are < pivot, a[i..j) are ≥ pivot, and a[j..hi) are unexamined. Both cases of the branch preserve it: a ≥ element simply extends the middle region by advancing j; a < element is swapped with the first ≥ element (at i), which extends the left region and shifts the middle region right by one. When j == hi, no unexamined elements remain, and one final swap puts the pivot between the two regions.
Dutch flag invariant: a[0..low) are 0s, a[low..mid) are 1s, a[mid..high] are unexamined, a(high..n) are 2s. Each iteration shrinks the unexamined region by exactly one index (either mid++ or high--), so the loop runs n times; when mid > high the unexamined region is empty and the three regions cover the array.
Hoare correctness: each scan stops on an element that belongs on the other side; swapping fixes both, and the pointers cross only when every element in the left part is ≤ p and every element in the right part is ≥ p. The scans cannot run off the array because the pivot itself acts as a sentinel for both directions.
Recognition
How to tell a problem wants this.
- "Sort an array of 0s, 1s, and 2s in one pass", "sort colors", "without using the library sort", "
O(1)extra space". - "Move all negative numbers before positive numbers", "put even numbers first", "segregate", "group by category" — two-way partition.
- Any problem where you must implement Quick Sort or Quickselect ("find the k-th largest element in
O(n)average"). - Many duplicate keys with a pivot — three-way partition avoids the quadratic trap.
- Statement gives an unsorted array and asks only that elements be *grouped* by a predicate, not fully sorted — full sorting would be
O(n log n)whereO(n)suffices.
Interactive visualization
Play, step, change the input. ← → and space work too.
1lo = 0, mid = 0, hi = n - 12while mid <= hi:3 if a[mid] == 0: swap(a[lo], a[mid]); lo++; mid++4 elif a[mid] == 1: mid++5 else: swap(a[mid], a[hi]); hi--Pseudocode
1# Dutch national flag: sort values 0/1/2 in place2low = 0, mid = 0, high = n - 13while mid <= high:4 if a[mid] == 0: swap(a[low], a[mid]); low += 1; mid += 15 elif a[mid] == 1: mid += 16 else: swap(a[mid], a[high]); high -= 1 # a[mid] is now unexamined7# Lomuto: partition a[lo..hi] around pivot a[hi]; returns pivot index8i = lo9for j in lo..hi-1:10 if a[j] < a[hi]: swap(a[i], a[j]); i += 111swap(a[i], a[hi]); return iImplementations
1# Sort Colors: three-way (Dutch national flag) partition of 0s, 1s and 2s in place2def sort_colors(a: list[int]) -> None:31 · Three region boundaries: [0,low) zeros, [low,mid) ones, (high,n) twos4 low = 05 mid = 06 high = len(a) - 172 · Examine the unknown region [mid, high]8 while mid <= high:9 if a[mid] == 0:103 · Zero goes to the left region; both low and mid advance11 a[low], a[mid] = a[mid], a[low]12 low += 113 mid += 114 elif a[mid] == 1:154 · One is already in place; only mid advances16 mid += 117 else:185 · Two goes to the right region; mid stays because a[mid] is unexamined19 a[mid], a[high] = a[high], a[mid]20 high -= 1- Tuple assignment
a[i], a[j] = a[j], a[i]is the Python swap; the right-hand side is evaluated fully before assignment. - The loop mirrors the invariant:
[0,low)zeros,[low,mid)ones,[mid,high]unknown,(high, n)twos. elifhandles the one case with no swap.- In the
elsebranch onlyhighmoves, so the swapped-in value is examined on the next iteration. - Return type
Nonecommunicates in-place mutation, consistent withlist.sort().
Any slicing (a[:low]) would copy; the algorithm uses index assignment only.
a.sort()is Timsort — O(n log n), and it would trivially "solve" the problem while missing the point.- The counting-sort alternative
a[:] = [0] * z + [1] * o + [2] * tis two passes and rebuilds the list in place via slice assignment. - Python has no
switch; theif / elif / elsechain is the idiom (ormatchon 3.10+).
- Rebinding
a = sorted(a)inside the function — the caller keeps the unsorted list. - Advancing
midin the2branch. - Using
while mid < highand leaving one element unexamined.
- Swap syntax: C++
std::swap(a[i], a[j]), JS/TS destructuring[a[i], a[j]] = [a[j], a[i]], Python tuple assignment — all in place, but only C++ is guaranteed allocation-free at the language level. - TypeScript can constrain the input to
(0 | 1 | 2)[]at compile time; the other languages must trust or validate the data at runtime. - Two-way partition exists in the C++ STL (
std::partition); none of the four languages ships a three-way partition, so this loop is written by hand everywhere.
Complexity
Lomuto: up to n swaps. Hoare: ≈ n/6 swaps on average. Three-way is essential when many keys equal the pivot.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Grouping elements by a predicate or into a small number of classes in one pass with
O(1)space. - The partition step of Quick Sort and Quickselect.
- Arrays with many duplicate keys — three-way partition keeps quicksort
O(n log n). - Sort Colors and any "sort with only 3 distinct values" problem.
- You need a stable grouping (relative order within each group preserved) — Lomuto/Hoare/Dutch flag all reorder within groups; use Two Pointers (Same Direction) with an extra buffer or a stable sort.
- More than a handful of distinct classes — use Counting Sort or a full sort.
- Linked lists — partitioning is possible by splicing into two lists, but the index-based schemes do not apply.
- When the data is already sorted and you only need a boundary — Binary Search finds it in
O(log n).
Alternatives
Common mistakes
- Dutch flag: advancing
midafter swapping withhigh— the element brought in from the right has not been examined. - Dutch flag: using
mid < highinstead ofmid <= high, leaving the last element unexamined. - Hoare: treating the returned index as the pivot's final position (it is not), or recursing on
[lo, j-1]and[j, hi]instead of[lo, j]and[j+1, hi]. - Hoare: using
while a[i] <= pivot— with equal keys the scan can run off the end; strict comparisons plus the pivot as sentinel keep it bounded. - Lomuto: forgetting the final swap that places the pivot, or using
<=in the comparison and gettingO(n²)on all-equal arrays.
Interview patterns
- Sort Colors (Dutch national flag) — the canonical three-pointer problem.
- Kth Largest Element via Quickselect: partition, then recurse into one side only.
- Partition array by parity / by sign / around a given value.
- Implement quicksort and discuss why three-way partitioning matters for duplicate-heavy inputs.
- Wiggle Sort II uses three-way partition around the median plus index mapping.
- Recognizing the approach from an array and a targetIntermediate
- When space complexity mattersIntermediate
- Two pointers or hash map?Intermediate
- Convincing me your algorithm is correctExpert
- Two SumBeginner