Dutch national flag (three-way partition)
Sort a list that holds only three kinds of values in one pass by swapping each item into a low, middle, or high zone.
O(n)Space O(1)4 practice problemsStep-through pictureThe idea
Some lists hold only three kinds of values: 0, 1, and 2, or low, medium, and high. You could count each kind and rewrite the list, but that takes two passes. The Dutch national flag method, named after a flag with three stripes, sorts the list in one pass and in place.
It keeps three indexes. Everything before low is a 0. Everything after high is a 2. Everything from low up to just before mid is a 1. The items from mid to high haven't been looked at yet. Each step looks at nums[mid] and swaps it into the right zone.
A 0 swaps to the low side and both low and mid move right. A 1 is already in the middle zone, so only mid moves. A 2 swaps to the high side and high moves left, but mid stays, because the item that came back from the end hasn't been looked at yet.
An everyday example
Sorting laundry into three piles on one shelf
You have one long shelf of folded clothes: whites, colors, and darks, all mixed. You push whites to the left end, darks to the right end, and colors stay in the middle. You check clothes one by one from the left. A white swaps into the next white spot. A color stays where it is. A dark swaps with whatever sits at the right end of the unchecked part, and since you haven't checked that item yet, you look at it next.
| In the example | In the code |
|---|---|
| The next spot for a white item | low |
| The item you are checking | nums[mid] |
| The next spot for a dark item | high |
| Swapping a white item to the left zone | [nums[low], nums[mid]] = [nums[mid], nums[low]] |
| Swapping a dark item to the right zone | [nums[mid], nums[high]] = [nums[high], nums[mid]] |
From the slow way to dutch national flag (three-way partition)
- Start with a general sort:
nums.sort((a, b) => a - b). It works, but it costsO(n log n)and ignores that there are only three different values. Knowing the values in advance should make this easier. - Counting is the next idea: count the
0s,1s, and2s in one pass, then write that many of each in a second pass. That isO(n), but it needs two passes, and it only works when the items are the values themselves. If each item is an object with apriorityfield, you can't rebuild the objects from three counts. - So move the real items instead. A read and write sweep can push all
0s to the front in one pass. Do that from both ends at once:0s are swapped toward the front,2s toward the back, and the1s are left in between, where they belong. - That needs three boundaries:
lowfor the next0slot,highfor the next2slot, andmidfor the item being checked. Each step looks atnums[mid]and either swaps it to one end or leaves it. That loop is the Dutch national flag partition.
How it works
- Set
low = 0,mid = 0, andhighto the last index. - While
mid <= high, look atnums[mid]. - If it's
0, swapnums[low]andnums[mid], then move bothlowandmidone step right. - If it's
1, it's already in the middle zone: movemidone step right. - If it's
2, swapnums[mid]andnums[high], then movehighone step left. Don't movemid: the item that just arrived is unchecked. - When
midpasseshigh, every item is in its zone and the list is sorted.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function sortThree(nums) {let low = 0;let mid = 0;let high = nums.length - 1;while (mid <= high) {if (nums[mid] === 0) {[nums[low], nums[mid]] = [nums[mid], nums[low]];low++;mid++;} else if (nums[mid] === 1) {mid++;} else {[nums[mid], nums[high]] = [nums[high], nums[mid]];high--;}}return nums;}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(1) |
Each step shrinks the unchecked zone by one, so there are n steps with at most one swap each. It only stores three indexes.
- Best case
O(n)All
1s, like[1, 1, 1, 1]: no swaps, justnsteps ofmid++. - Average case
O(n)Any mix: exactly
nsteps, each with at most one swap. - Worst case
O(n)Still
nsteps. The most swaps happen when there are no1s at all, like[2, 2, 2, 2], where every step is a swap, some of an item with itself.
Common mistakes
- Moving
midforward after swapping a2to the end. The item that came back is never checked, and a0can end up stuck in the middle. - Looping with
mid < high. The last unchecked item is skipped whenmidequalshigh. - Using it for more than three kinds of values. With four kinds, the zones don't cover every case; run a partition twice or count instead.
- Expecting equal items to keep their original order. The swaps move items around, so this sort is not stable; don't use it when items with the same key must stay in order.
When to use it
Look for these clues in a problem:
- The values come in exactly three kinds, such as
0,1,2or "less than, equal to, more than" a pivot. - The list must be sorted or grouped in place with
O(1)extra memory. - The question asks for one pass, so counting first and rewriting later is not allowed.
- You are writing a quick sort that handles many repeated values.
Check yourself
Answer each one in your head first, then open it.
Why does mid move after swapping a 0 but not after swapping a 2?
The item that comes from low is a 1 from the middle zone (or the same 0), so it's already checked. The item that comes from high is unchecked and could be anything.
Why is the condition mid <= high and not mid < high?
When mid === high, one unchecked item remains. With <, it would be skipped, and a 0 there would stay in the wrong place.
How many loop steps does a list of 10 items take?
Exactly 10. Each step shrinks the unchecked zone by one item, and it starts with all 10.
Is this sort stable?
No. A 2 swapped to the end can jump over other 2s, so equal items can change order.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 4 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The loop keeps four zones true at every step: indexes 0 to low - 1 hold 0s, low to mid - 1 hold 1s, mid to high are unchecked, and high + 1 to the end hold 2s. At the start, low and mid are 0 and high is the last index, so the first, second, and fourth zones are empty and everything is unchecked.
Each case keeps the zones. A 0 at mid swaps with nums[low], which is the first 1 (or the same item when the 1 zone is empty), so after low++ and mid++ both zones are still right. A 1 just joins the 1 zone with mid++. A 2 swaps with nums[high] and high-- grows the 2 zone; the item that came back is unchecked, which is exactly why mid stays.
Every step shrinks the unchecked zone by one: either mid moves right or high moves left. It starts with n items, so the loop runs exactly n times. When it ends, the unchecked zone is empty, and the other three zones cover the whole list in order: all 0s, then 1s, then 2s.
Going deeper
3 more ideas
The four zones the loop keeps
At every step: indexes 0 to low - 1 hold 0s, indexes low to mid - 1 hold 1s, indexes mid to high are not checked yet, and indexes high + 1 to the end hold 2s. Each case of the loop shrinks the unchecked zone by one and keeps the other three zones correct. When the unchecked zone is empty, the list is sorted.
Why mid moves after a 0 swap but not after a 2 swap
When nums[mid] is 0, it swaps with nums[low]. That item came from the 1 zone (or is the same item), so it is a known 1 and mid can move on. When nums[mid] is 2, it swaps with nums[high], which came from the unchecked zone. It could be a 0, 1, or 2, so mid must look at it before moving.
Why it takes O(n) time
Every step either moves mid right or moves high left, so the unchecked zone shrinks by one item per step. It starts with n items, so there are exactly n steps, each with at most one swap. The three indexes are the only extra memory.
Work it by hand
Every step in a table, with why
Sort 7 items. A 2 swapped from the end brings back a 1, and a later 2 brings back a 0, so mid has to stay put twice.
nums = [1, 2, 0, 1, 2, 0, 1]
| Step | low | mid | high | nums[mid] | nums after | What happens | Why |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 0 | 6 | 1 | [1, 2, 0, 1, 2, 0, 1] | A 1: mid++ | A 1 already belongs in the middle zone, so only mid moves. |
| 2 | 0 | 1 | 6 | 2 | [1, 1, 0, 1, 2, 0, 2] | A 2: swap with nums[6], high-- | The 2 goes to the end. A 1 comes back from index 6, unchecked, so mid stays at 1. |
| 3 | 0 | 1 | 5 | 1 | [1, 1, 0, 1, 2, 0, 2] | A 1: mid++ | The item that came back is a 1, so now mid can move on. |
| 4 | 0 | 2 | 5 | 0 | [0, 1, 1, 1, 2, 0, 2] | A 0: swap with nums[0], both move | The 0 swaps with the first 1. The 1 it gets back is a known 1, so low and mid both move. |
| 5 | 1 | 3 | 5 | 1 | [0, 1, 1, 1, 2, 0, 2] | A 1: mid++ | Three 1s now sit together in the middle zone. |
| 6 | 1 | 4 | 5 | 2 | [0, 1, 1, 1, 0, 2, 2] | A 2: swap with nums[5], high-- | This time a 0 comes back from the end. mid stays to check it. |
| 7 | 1 | 4 | 4 | 0 | [0, 0, 1, 1, 1, 2, 2] | A 0: swap with nums[1], both move | The 0 goes to index 1 and a 1 moves to index 4. Now mid is 5 and high is 4. |
It returns [0, 0, 1, 1, 1, 2, 2] after 7 steps, one per item. The loop stops because mid (5) has passed high (4): no unchecked items remain.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Dutch national flag (three-way partition) | O(n) | O(1) | There are exactly three kinds, the items must move in place, and one pass is required. |
| Counting sort | O(n) | O(1) | The items are plain values, two passes are fine, and you want the simplest code. |
Built-in sort with a compare function | O(n log n) | O(n) | The list is small, or equal items must keep their order: JavaScript's sort is stable. |
Extra memory: O(1): three indexes. The swaps happen inside nums.
How the steps grow
- Dutch national flag (three-way partition), Counting sort
O(n) - Built-in
sortwith a compare functionO(n log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Dutch national flag (three-way partition) | 10 | 1,000 | 1 million |
| Counting sort | 10 | 1,000 | 1 million |
Built-in sort with a compare function | 33 | 9,966 | 20 million |
Inputs that need care
5 tricky inputs
- An empty list:
nums = [] highstarts at-1, somid <= highis false and the empty list is returned as it is.- All
2s:nums = [2, 2, 2, 2] - Every step swaps
nums[mid]withnums[high]and moveshighleft whilemidstays at 0. The last swap is index 0 with itself. Four swaps, and the list is unchanged, which is correct. - All
0s:nums = [0, 0, 0] - Each
0swaps with itself, becauselow === midthe whole time. Three harmless swaps. - A value other than
0,1, or2:nums = [1, 3, 0, -1, 2] - The
elsebranch treats anything that isn't0or1as a2, so3and even-1go to the high zone: the result is[0, 1, -1, 2, 3]. Validate the input, or compare with a pivot instead. - Objects with equal keys that must keep their order
- Swaps move items across long distances, so two items with the same key can change order. This partition isn't stable; use a stable sort or three separate lists if the order matters.
Variations and follow-ups
Twists interviewers add
Partition around a pivot
Replace the tests with nums[mid] < pivot, nums[mid] === pivot, and nums[mid] > pivot. This three-way partition is how quick sort stays fast on lists with many repeated values: all items equal to the pivot are finished in one pass.
Counting sort
When items are just the values 0, 1, 2, counting each value and rewriting the list is also O(n) and very simple. It takes two passes and can't move objects that carry other data.
Two-way partition
With only two kinds of values, such as even and odd, one boundary is enough: a read and write sweep, or two pointers from opposite ends that swap a misplaced pair.
More than three kinds
For four kinds, run a two-way partition to split {0, 1} from {2, 3}, then partition each half. Or count, if the values are small integers.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "only three values", "group into low, medium, high", or "sort
0s,1s, and2s in one pass and in place". - Draw the four zones before coding, and say which index bounds each one. Once the zones are clear, each branch of the loop writes itself.
- Say why
middoesn't move after the2swap: the item that came fromhighis unchecked. Interviewers often ask exactly this, and it is the most common bug. - Expect the follow-up "can you do it with a pivot, for quick sort?". The answer is the same loop with
< pivot,=== pivot, and> pivottests.
Where frontend code uses it
The same idea in real UI code
- Grouping a list of notifications by severity (errors, warnings, info) in place before rendering, when the list is large and memory is tight.
- Quick sort with three-way partitioning, which splits items into less than, equal to, and greater than a pivot. It stays fast on lists with many repeated values, such as table rows sorted by a status column.
- Ordering task cards in a board column into blocked, in progress, and done groups with one pass over the array.