advanced10 min read·Updated October 7, 2026
Quicksort with duplicates (3-way) Explained: Tracing [2,2,2,1,2,3]
Master 3-way Quicksort by tracing duplicate array [2,2,2,1,2,3] vs sorted data. Build mental models for the Dutch National Flag partition algorithm.
By Learnisim AI·Published October 7, 2026
LEARNING ARCSetup → Model in the example → Full trace → Twist → Edge cases → Apply
PREREQUISITES
- Basic Quicksort and partitioning
- Recursion and pointer manipulation
Why
Why Quicksort Fails on Sorted Data and Heavy Duplicates
Phase 1: The Duplicate and Sorted Bottleneck
Imagine you are handed an array that is already sorted, like
[1, 2, 3, 4, 5], and your partition strategy always picks the last element as the pivot. In a standard two-way partition, you split the array into two regions: elements less than the pivot and elements greater than or equal to the pivot. Because 5 is already the maximum, every preceding element lands on the left side. Your partition shrinks the problem size by only one element per recursive call, leading to a disastrous time complexity.Now consider a second scenario where your array is packed with identical values, such as
[2, 2, 2, 1, 2, 3] with a pivot of 2. In a traditional two-way scheme, all those duplicate 2s get lumped together with either the smaller or larger partitions. They are treated as distinct recursive burdens, even though they are already in their final relative positions relative to the pivot. You end up wasting precious cycles repeatedly partitioning items that never needed to move.Without an adaptive strategy, standard Quicksort becomes painfully inefficient when real-world data contains clustered keys, categorical attributes, or pre-sorted segments. We need a way to instantly isolate items equal to the pivot so we can bypass them entirely in future recursive steps.
Model
The Dutch National Flag Model for 3-Way Partitioning
Phase 2: The Three-Way Partitioning Mental Model
Standard 2-way partitioning splits an array into two zones around a pivot: elements less than or equal to the left, and elements greater on the right. When applied to our duplicate-heavy working example array with a pivot of 2, everything equal to the pivot gets shoved arbitrarily into one of those two zones, forcing redundant recursive calls. To solve this, Edsger Dijkstra's Dutch National Flag algorithm introduces a third zone specifically for items equal to the pivot.
The mental model uses four distinct pointers to continuously sweep and categorize an array into four regions during a single pass:
-
-
-
-
low: boundary where elements strictly less than the pivot end (everything left of low is ).-
i: current scanning pointer examining the element.-
mid: boundary where elements equal to the pivot end (everything between low and mid is ). Unexamined elements sit between mid and high.-
high: boundary where elements strictly greater than the pivot begin (everything right of high is )...[ < pivot | = pivot | unexamined | > pivot ]
^ ^ ^ ^
low mid i high
^ ^ ^ ^
low mid i high
As our scanning pointer
i moves from mid up to high, any element equal to the pivot simply extends the middle zone without triggering further recursive work, directly tackling the trap of redundant elements.Try this: Given the array [2, 2, 2, 1, 2, 3] with pivot = 2, trace how a 3-way partition would designate the initial regions before scanning starts.
Worked example
Tracing 2-Way Quadratic Collapse Versus 3-Way Duplicate Isolation
Phase 3: Worked Example
To see why standard Quicksort struggles where 3-way partitioning thrives, we trace two contrasting scenarios from our locked example. First, we examine standard 2-way partitioning on the sorted array
[1, 2, 3, 4, 5] using the last element as the pivot. Second, we examine 3-way partitioning on the duplicate-heavy array [2, 2, 2, 1, 2, 3] with pivot 2.Given
- Scenario A (2-Way): Array[1, 2, 3, 4, 5], pivot = last element.- Scenario B (3-Way): Array
[2, 2, 2, 1, 2, 3], pivot , pointers set up via the Dutch National Flag invariant (, , ).Steps
Scenario A Trace (Standard 2-Way)
1. Pass 1: Pivot is 5. Array is[1, 2, 3, 4, 5]. Elements are scanned; 5 is placed at the end. Subproblem size for the left recursive call is ([1, 2, 3, 4]).2. Pass 2: Pivot is 4. Subproblem size is (
[1, 2, 3]).3. Pass 3: Pivot is 3. Subproblem size is (
[1, 2]).4. Pass 4: Pivot is 2. Subproblem size is (
[1]).Scenario B Trace (3-Way)
Initial array:[2, 2, 2, 1, 2, 3], pivot .Pointers start at: , , .
1. Step 1 (): Element at is 2 (equal to ). We increment : , , .
2. Step 2 (): Element at is 2 (equal to ). We increment : , , .
3. Step 3 (): Element at is 2 (equal to ). We increment : , , .
4. Step 4 (): Element at is 1 (less than ). We swap index with , then increment both and . Array becomes
5. Step 5 (): Element at is 2 (equal to ). We increment : , , .
6. Step 6 (): Element at is 3 (greater than ). We swap index with , then decrement . Array becomes
2. Step 2 (): Element at is 2 (equal to ). We increment : , , .
3. Step 3 (): Element at is 2 (equal to ). We increment : , , .
4. Step 4 (): Element at is 1 (less than ). We swap index with , then increment both and . Array becomes
[1, 2, 2, 2, 2, 3], , , .5. Step 5 (): Element at is 2 (equal to ). We increment : , , .
6. Step 6 (): Element at is 3 (greater than ). We swap index with , then decrement . Array becomes
[1, 2, 2, 2, 3, 2], , , .Result
- In Scenario A, recursion depth reaches , leading to comparisons because each step only shrinks the array by 1.- In Scenario B, all duplicate 2s are safely gathered between pointer and pointer . The subsequent recursive calls will only be invoked on the strictly smaller region (
[1]) and the strictly larger region ([3, 2] before final normalization), completely bypassing redundant duplicate scans.Try this: Given the array [2, 2, 2, 1, 2, 3] after Step 4 where array is [1, 2, 2, 2, 2, 3] with lt=1, i=4, gt=5, predict the exact array configuration and pointer values after processing index i=4.
Practice
Predicting Pointer Movements on a Duplicate-Heavy Partition Step
Phase 4: Practice
Now it is time to test your mental model against the mechanics of 3-way partitioning. Recall our working array with heavy duplicates:
[2, 2, 2, 1, 2, 3]. Suppose we choose a pivot value of 2 and run a single Dutch National Flag partitioning step on an array segment where lt, i, and gt have already advanced partway through the sequence.Consider the moment when our scan pointer
i lands on an element equal to the pivot 2. Based on the rules of 3-way partitioning established in our model, determine exactly how the pointers (lt, i, and gt) and the array elements should shift.The Task:
Write down or mentally trace the exact invariant conditions for what happens when
Write down or mentally trace the exact invariant conditions for what happens when
arr[i] == pivot during a 3-way partition pass. Which pointer advances, which pointer stays put, and which region expands?- A)
- B)
- C)
- D)
lt increments, i increments, gt is untouched- B)
lt stays put, i increments, gt is untouched- C)
lt stays put, i increments, gt decrements- D)
lt increments, i stays put, gt decrementsTry this: Given arr = [2, 2, 2, 1, 2, 3] with pivot = 2.
Trace the pointer updates when arr[i] matches the pivot.
Trace the pointer updates when arr[i] matches the pivot.
Apply
Transferring 3-Way Partitioning to Array Segregation and Beyond
Phase 5: Transfer
Now that you have mastered how 3-way partitioning groups identical elements into an isolated middle bucket—preventing the collapse seen on our duplicate-heavy array
[2, 2, 2, 1, 2, 3]—we can transfer this exact three-way pointer pattern (lt, i, gt) to other data restructuring challenges. The core pattern of maintaining boundary pointers to fan elements into three distinct categories is not just for sorting; it is a universal template for single-pass partitioning.Consider the classic Dutch National Flag problem, where an array contains only three distinct values representing colors (for example,
0, 1, and 2), and you must sort them in-place in a single pass. By mapping 0 to the < pivot region, 1 to the = pivot region, and 2 to the > pivot region, Dijkstra's 3-way partitioning algorithm solves this exact color-sorting puzzle without any modification to the pointer mechanics we used on [2, 2, 2, 1, 2, 3].Another powerful transfer is Quicksort with multiple distinct keys or custom multi-criteria sorting where duplicate keys dominate real-world records. When sorting database rows or objects where a primary key or status field frequently repeats, standard 2-way partitioning repeatedly re-processes identical keys, whereas the 3-way approach bounds the recursion strictly to elements strictly less than and strictly greater than the pivot.
To test your transfer capability, consider how you would adapt this logic if your array contained four distinct categories instead of three. While a 3-way partition uses four pointers (
low, lt, i, gt, high), scaling to categories requires maintaining boundary pointers to manage each distinct category in a single linear sweep.Question
Suppose you are given an array containing only three distinct integer values:
0, 1, and 2, representing red, white, and blue elements respectively. Explain how the pointer mechanics of 3-way partitioning (using pointers lt, i, and gt) sort this array in a single pass of time, and state which regions each pointer bounds.FAQ
How does 3-way partitioning handle the duplicate array [2,2,2,1,2,3] compared to standard Quicksort?
Standard Quicksort treats equals as regular elements, leading to unbalanced partitions and redundant work. 3-way partitioning uses the Dutch National Flag model to group all elements equal to the pivot (like the 2s) directly in the middle, excluding them from future recursive calls.
Why does standard Quicksort fail on sorted data like [1, 2, 3, 4, 5]?
When always picking the last element as the pivot on sorted data, the partition splits the array into size n-1 and 0, resulting in O(n²) quadratic time complexity due to a recursion depth of n.
What is the time complexity of 3-way Quicksort on arrays with many duplicate keys?
It runs in O(n log k) time where k is the number of unique keys, approaching O(n) linear time when there are very few unique values, because duplicates are immediately isolated.
What are the core pointers used in the Dutch National Flag partition?
Four regions are maintained using pointers: less than pivot (<lt), equal to pivot (=eq), unexamined (scan), and greater than pivot (>gt).