Chapter 18: Efficient and Specialized Sorting
18.02Beginner / Intermediate6 topics26 min2 interactive animations

Quick sort

Quick sort picks a pivot, partitions the array so smaller values sit left of it and larger values right, then sorts each side. It is usually the fastest comparison sort in practice, but a bad pivot choice makes it quadratic.

Learning goals

  • Trace Lomuto and Hoare partitioning step by step
  • Compare pivot strategies and explain which input breaks each one
  • Use three-way partitioning to sort duplicate-heavy data quickly
  • Keep recursion depth at O(log n) with a loop on the larger side

Prerequisites

RecursionSwappingMerge sortExpected value (intuition)

To sort a class by height, pick one student and split everyone into shorter and taller. That student is now in the right spot. Repeat on each group. If you always happen to pick the shortest student, the split is useless: that is the worst case.

18.02.1

Partition schemes Lomuto/Hoare

Lomuto keeps one boundary i: everything left of i is smaller than the pivot. A scan pointer j moves right, and each smaller value is swapped into position i. At the end the pivot is swapped into index i, its final position. It is short and easy to get right.

Hoare uses two pointers that start at the ends and move toward each other. i stops at a value that belongs on the right, j stops at a value that belongs on the left, and they swap. It does about three times fewer swaps than Lomuto on average, but the pivot does not finish at a known index, so the recursion is on [lo..p] and [p+1..hi].

LomutoHoare
PointersOne scan pointer and one boundaryTwo pointers moving inward
Pivot after partitionAt its final index pNot at a known index
Recursion[lo..p-1] and [p+1..hi][lo..p] and [p+1..hi]
SwapsMoreAbout 3x fewer on average
Many equal keysDegrades toward O(n²)Splits equal keys evenly, stays fast

Best

O(n log n)

Average

O(n log n)

Worst

O(n²)

Space

O(log n) stack

Quick sort (Lomuto partition)

Interactive · edit the input

Your input

Try

int[] arr · length 8

38
27
43
3
9
82
10
15
01234567
PivotBeing comparedLess than pivotFinal positionOutside this call
Setup

Pick a pivot, partition around it, then sort each side. Pivot strategy: last element.

Step 1 / 61
Java
1public static void quickSort(int[] arr, int lo, int hi) {
2 if (lo >= hi) return;
3 int p = partition(arr, lo, hi);
4 quickSort(arr, lo, p - 1);
5 quickSort(arr, p + 1, hi);
6}
7
8static int partition(int[] arr, int lo, int hi) {
9 movePivotToEnd(arr, lo, hi); // strategy: last, first, middle, median of 3
10 int pivot = arr[hi];
11 int i = lo; // arr[lo..i-1] < pivot
12 for (int j = lo; j < hi; j++) {
13 if (arr[j] < pivot) {
14 swap(arr, i, j);
15 i++;
16 }
17 }
18 swap(arr, i, hi); // pivot to its final place
19 return i;
20}

Live state

0 comparisons0 swaps

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 61 steps
StepLineStateWhat happens
1L1n=8Pick a pivot, partition around it, then sort each side. Pivot strategy: last element.
2L1lo=0 hi=7Enter quickSort(arr, 0, 7).
3L2lo=0 hi=7Two or more elements, so partition.
4L9lo=0 hi=7Pivot strategy: last element. The pivot is already at arr[7].
5L11pivot=15 i=0pivot = 15. i marks where the next value smaller than the pivot will go.
6L13pivot=15 i=0 j=0Not smaller: leave it for the right region.
7L13pivot=15 i=0 j=1Not smaller: leave it for the right region.
8L13pivot=15 i=0 j=2Not smaller: leave it for the right region.
9L13pivot=15 i=0 j=3Smaller than the pivot: it belongs in the left region.
10L14pivot=15 i=1 j=3Swap arr[0] and arr[3], then i = 1.
11L13pivot=15 i=1 j=4Smaller than the pivot: it belongs in the left region.
12L14pivot=15 i=2 j=4Swap arr[1] and arr[4], then i = 2.
13L13pivot=15 i=2 j=5Not smaller: leave it for the right region.
14L13pivot=15 i=2 j=6Smaller than the pivot: it belongs in the left region.
15L14pivot=15 i=3 j=6Swap arr[2] and arr[6], then i = 3.
16L18pivot=15 p=3Swap the pivot into index 3. Everything left of it is < 15, everything right is >= 15, and 15 will never move again.
17L4lo=0 hi=2Enter quickSort(arr, 0, 2).
18L2lo=0 hi=2Two or more elements, so partition.
19L9lo=0 hi=2Pivot strategy: last element. The pivot is already at arr[2].
20L11pivot=10 i=0pivot = 10. i marks where the next value smaller than the pivot will go.
21L13pivot=10 i=0 j=0Smaller than the pivot: it belongs in the left region.
22L14pivot=10 i=1 j=0arr[0] is already in place; i advances to 1.
23L13pivot=10 i=1 j=1Smaller than the pivot: it belongs in the left region.
24L14pivot=10 i=2 j=1arr[1] is already in place; i advances to 2.
25L18pivot=10 p=2Swap the pivot into index 2. Everything left of it is < 10, everything right is >= 10, and 10 will never move again.
26L4lo=0 hi=1Enter quickSort(arr, 0, 1).
27L2lo=0 hi=1Two or more elements, so partition.
28L9lo=0 hi=1Pivot strategy: last element. The pivot is already at arr[1].
29L11pivot=9 i=0pivot = 9. i marks where the next value smaller than the pivot will go.
30L13pivot=9 i=0 j=0Smaller than the pivot: it belongs in the left region.
31L14pivot=9 i=1 j=0arr[0] is already in place; i advances to 1.
32L18pivot=9 p=1Swap the pivot into index 1. Everything left of it is < 9, everything right is >= 9, and 9 will never move again.
33L4lo=0 hi=0Enter quickSort(arr, 0, 0).
34L2lo=0 hi=0Zero or one element: already sorted.
35L5lo=2 hi=1Enter quickSort(arr, 2, 1).
36L2lo=2 hi=1Zero or one element: already sorted.
37L5lo=3 hi=2Enter quickSort(arr, 3, 2).
38L2lo=3 hi=2Zero or one element: already sorted.
39L5lo=4 hi=7Enter quickSort(arr, 4, 7).
40L2lo=4 hi=7Two or more elements, so partition.
41L9lo=4 hi=7Pivot strategy: last element. The pivot is already at arr[7].
42L11pivot=38 i=4pivot = 38. i marks where the next value smaller than the pivot will go.
43L13pivot=38 i=4 j=4Smaller than the pivot: it belongs in the left region.
44L14pivot=38 i=5 j=4arr[4] is already in place; i advances to 5.
45L13pivot=38 i=5 j=5Not smaller: leave it for the right region.
46L13pivot=38 i=5 j=6Not smaller: leave it for the right region.
47L18pivot=38 p=5Swap the pivot into index 5. Everything left of it is < 38, everything right is >= 38, and 38 will never move again.
48L4lo=4 hi=4Enter quickSort(arr, 4, 4).
49L2lo=4 hi=4Zero or one element: already sorted.
50L5lo=6 hi=7Enter quickSort(arr, 6, 7).
51L2lo=6 hi=7Two or more elements, so partition.
52L9lo=6 hi=7Pivot strategy: last element. The pivot is already at arr[7].
53L11pivot=82 i=6pivot = 82. i marks where the next value smaller than the pivot will go.
54L13pivot=82 i=6 j=6Smaller than the pivot: it belongs in the left region.
55L14pivot=82 i=7 j=6arr[6] is already in place; i advances to 7.
56L18pivot=82 p=7Swap the pivot into index 7. Everything left of it is < 82, everything right is >= 82, and 82 will never move again.
57L4lo=6 hi=6Enter quickSort(arr, 6, 6).
58L2lo=6 hi=6Zero or one element: already sorted.
59L5lo=8 hi=7Enter quickSort(arr, 8, 7).
60L2lo=8 hi=7Zero or one element: already sorted.
61L6Sorted with 14 comparisons and 5 swaps.

Quick sort (Hoare partition)

Interactive · edit the input

Your input

Try

int[] arr · length 8

38
27
43
3
9
82
10
15
01234567
Pivot valueBeing comparedMovedSingle element doneOutside this call
Setup

Hoare's scheme does fewer swaps than Lomuto on average, but the pivot does not end at a fixed index.

Step 1 / 110
Java
1public static void quickSort(int[] arr, int lo, int hi) {
2 if (lo >= hi) return;
3 int p = partition(arr, lo, hi);
4 quickSort(arr, lo, p);
5 quickSort(arr, p + 1, hi);
6}
7
8static int partition(int[] arr, int lo, int hi) {
9 int pivot = arr[lo + (hi - lo) / 2];
10 int i = lo - 1, j = hi + 1;
11 while (true) {
12 do { i++; } while (arr[i] < pivot);
13 do { j--; } while (arr[j] > pivot);
14 if (i >= j) return j;
15 swap(arr, i, j);
16 }
17}

Live state

0 comparisons0 swaps

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 110 steps
StepLineStateWhat happens
1L1n=8Hoare's scheme does fewer swaps than Lomuto on average, but the pivot does not end at a fixed index.
2L1lo=0 hi=7Enter quickSort(arr, 0, 7).
3L9lo=0 hi=7 pivot=3pivot = arr[3] = 3. Two pointers will walk toward each other.
4L10pivot=3 i=-1 j=8i starts just left of the window and j just right of it.
5L12pivot=3 i=0 j=8i stops: this value belongs on the right side.
6L13pivot=3 i=0 j=7Larger than the pivot, so it is on the correct side. Move j left.
7L13pivot=3 i=0 j=6Larger than the pivot, so it is on the correct side. Move j left.
8L13pivot=3 i=0 j=5Larger than the pivot, so it is on the correct side. Move j left.
9L13pivot=3 i=0 j=4Larger than the pivot, so it is on the correct side. Move j left.
10L13pivot=3 i=0 j=3j stops: this value belongs on the left side.
11L14pivot=3 i=0 j=3i and j both stopped on misplaced values, so swap them.
12L15pivot=3 i=0 j=3Swapped arr[0] and arr[3].
13L12pivot=3 i=1 j=3i stops: this value belongs on the right side.
14L13pivot=3 i=1 j=2Larger than the pivot, so it is on the correct side. Move j left.
15L13pivot=3 i=1 j=1Larger than the pivot, so it is on the correct side. Move j left.
16L13pivot=3 i=1 j=0j stops: this value belongs on the left side.
17L14pivot=3 i=1 j=0The pointers met or crossed. Split point is j = 0.
18L3p=0Partition returns 0: [0..0] holds values <= pivot and [1..7] holds values >= pivot. The pivot itself is not necessarily in its final place.
19L4lo=0 hi=0Enter quickSort(arr, 0, 0).
20L2lo=0 hi=0Zero or one element: already sorted.
21L5lo=1 hi=7Enter quickSort(arr, 1, 7).
22L9lo=1 hi=7 pivot=9pivot = arr[4] = 9. Two pointers will walk toward each other.
23L10pivot=9 i=0 j=8i starts just left of the window and j just right of it.
24L12pivot=9 i=1 j=8i stops: this value belongs on the right side.
25L13pivot=9 i=1 j=7Larger than the pivot, so it is on the correct side. Move j left.
26L13pivot=9 i=1 j=6Larger than the pivot, so it is on the correct side. Move j left.
27L13pivot=9 i=1 j=5Larger than the pivot, so it is on the correct side. Move j left.
28L13pivot=9 i=1 j=4j stops: this value belongs on the left side.
29L14pivot=9 i=1 j=4i and j both stopped on misplaced values, so swap them.
30L15pivot=9 i=1 j=4Swapped arr[1] and arr[4].
31L12pivot=9 i=2 j=4i stops: this value belongs on the right side.
32L13pivot=9 i=2 j=3Larger than the pivot, so it is on the correct side. Move j left.
33L13pivot=9 i=2 j=2Larger than the pivot, so it is on the correct side. Move j left.
34L13pivot=9 i=2 j=1j stops: this value belongs on the left side.
35L14pivot=9 i=2 j=1The pointers met or crossed. Split point is j = 1.
36L3p=1Partition returns 1: [1..1] holds values <= pivot and [2..7] holds values >= pivot. The pivot itself is not necessarily in its final place.
37L4lo=1 hi=1Enter quickSort(arr, 1, 1).
38L2lo=1 hi=1Zero or one element: already sorted.
39L5lo=2 hi=7Enter quickSort(arr, 2, 7).
40L9lo=2 hi=7 pivot=27pivot = arr[4] = 27. Two pointers will walk toward each other.
41L10pivot=27 i=1 j=8i starts just left of the window and j just right of it.
42L12pivot=27 i=2 j=8i stops: this value belongs on the right side.
43L13pivot=27 i=2 j=7j stops: this value belongs on the left side.
44L14pivot=27 i=2 j=7i and j both stopped on misplaced values, so swap them.
45L15pivot=27 i=2 j=7Swapped arr[2] and arr[7].
46L12pivot=27 i=3 j=7i stops: this value belongs on the right side.
47L13pivot=27 i=3 j=6j stops: this value belongs on the left side.
48L14pivot=27 i=3 j=6i and j both stopped on misplaced values, so swap them.
49L15pivot=27 i=3 j=6Swapped arr[3] and arr[6].
50L12pivot=27 i=4 j=6i stops: this value belongs on the right side.
51L13pivot=27 i=4 j=5Larger than the pivot, so it is on the correct side. Move j left.
52L13pivot=27 i=4 j=4j stops: this value belongs on the left side.
53L14pivot=27 i=4 j=4The pointers met or crossed. Split point is j = 4.
54L3p=4Partition returns 4: [2..4] holds values <= pivot and [5..7] holds values >= pivot. The pivot itself is not necessarily in its final place.
55L4lo=2 hi=4Enter quickSort(arr, 2, 4).
56L9lo=2 hi=4 pivot=10pivot = arr[3] = 10. Two pointers will walk toward each other.
57L10pivot=10 i=1 j=5i starts just left of the window and j just right of it.
58L12pivot=10 i=2 j=5i stops: this value belongs on the right side.
59L13pivot=10 i=2 j=4Larger than the pivot, so it is on the correct side. Move j left.
60L13pivot=10 i=2 j=3j stops: this value belongs on the left side.
61L14pivot=10 i=2 j=3i and j both stopped on misplaced values, so swap them.
62L15pivot=10 i=2 j=3Swapped arr[2] and arr[3].
63L12pivot=10 i=3 j=3i stops: this value belongs on the right side.
64L13pivot=10 i=3 j=2j stops: this value belongs on the left side.
65L14pivot=10 i=3 j=2The pointers met or crossed. Split point is j = 2.
66L3p=2Partition returns 2: [2..2] holds values <= pivot and [3..4] holds values >= pivot. The pivot itself is not necessarily in its final place.
67L4lo=2 hi=2Enter quickSort(arr, 2, 2).
68L2lo=2 hi=2Zero or one element: already sorted.
69L5lo=3 hi=4Enter quickSort(arr, 3, 4).
70L9lo=3 hi=4 pivot=15pivot = arr[3] = 15. Two pointers will walk toward each other.
71L10pivot=15 i=2 j=5i starts just left of the window and j just right of it.
72L12pivot=15 i=3 j=5i stops: this value belongs on the right side.
73L13pivot=15 i=3 j=4Larger than the pivot, so it is on the correct side. Move j left.
74L13pivot=15 i=3 j=3j stops: this value belongs on the left side.
75L14pivot=15 i=3 j=3The pointers met or crossed. Split point is j = 3.
76L3p=3Partition returns 3: [3..3] holds values <= pivot and [4..4] holds values >= pivot. The pivot itself is not necessarily in its final place.
77L4lo=3 hi=3Enter quickSort(arr, 3, 3).
78L2lo=3 hi=3Zero or one element: already sorted.
79L5lo=4 hi=4Enter quickSort(arr, 4, 4).
80L2lo=4 hi=4Zero or one element: already sorted.
81L5lo=5 hi=7Enter quickSort(arr, 5, 7).
82L9lo=5 hi=7 pivot=38pivot = arr[6] = 38. Two pointers will walk toward each other.
83L10pivot=38 i=4 j=8i starts just left of the window and j just right of it.
84L12pivot=38 i=5 j=8i stops: this value belongs on the right side.
85L13pivot=38 i=5 j=7Larger than the pivot, so it is on the correct side. Move j left.
86L13pivot=38 i=5 j=6j stops: this value belongs on the left side.
87L14pivot=38 i=5 j=6i and j both stopped on misplaced values, so swap them.
88L15pivot=38 i=5 j=6Swapped arr[5] and arr[6].
89L12pivot=38 i=6 j=6i stops: this value belongs on the right side.
90L13pivot=38 i=6 j=5j stops: this value belongs on the left side.
91L14pivot=38 i=6 j=5The pointers met or crossed. Split point is j = 5.
92L3p=5Partition returns 5: [5..5] holds values <= pivot and [6..7] holds values >= pivot. The pivot itself is not necessarily in its final place.
93L4lo=5 hi=5Enter quickSort(arr, 5, 5).
94L2lo=5 hi=5Zero or one element: already sorted.
95L5lo=6 hi=7Enter quickSort(arr, 6, 7).
96L9lo=6 hi=7 pivot=82pivot = arr[6] = 82. Two pointers will walk toward each other.
97L10pivot=82 i=5 j=8i starts just left of the window and j just right of it.
98L12pivot=82 i=6 j=8i stops: this value belongs on the right side.
99L13pivot=82 i=6 j=7j stops: this value belongs on the left side.
100L14pivot=82 i=6 j=7i and j both stopped on misplaced values, so swap them.
101L15pivot=82 i=6 j=7Swapped arr[6] and arr[7].
102L12pivot=82 i=7 j=7i stops: this value belongs on the right side.
103L13pivot=82 i=7 j=6j stops: this value belongs on the left side.
104L14pivot=82 i=7 j=6The pointers met or crossed. Split point is j = 6.
105L3p=6Partition returns 6: [6..6] holds values <= pivot and [7..7] holds values >= pivot. The pivot itself is not necessarily in its final place.
106L4lo=6 hi=6Enter quickSort(arr, 6, 6).
107L2lo=6 hi=6Zero or one element: already sorted.
108L5lo=7 hi=7Enter quickSort(arr, 7, 7).
109L2lo=7 hi=7Zero or one element: already sorted.
110L6Sorted with 43 comparisons and 7 swaps.
18.02.2

Pivot selection

The pivot decides how balanced the split is. A good pivot is near the median, so each side is about half the array and the recursion has log₂ n levels. A pivot that is the minimum or maximum leaves one side empty and the other with n - 1 elements.

Common strategies are the last or first element (simple, but terrible on sorted input), the middle element (fine for sorted input, but an adversary can still build a bad input), median of three (take the median of first, middle and last), and a random element. In the animation, change the Pivot option and run “Sorted, last pivot (worst)” and then “Sorted, middle pivot”: 28 comparisons versus 13 for 8 values.

StrategySorted inputReverse inputWhy
First or lastO(n²)O(n²)Pivot is always an extreme value
MiddleO(n log n)O(n log n)Pivot is the median of sorted data
Median of 3O(n log n)O(n log n)Protects against sorted and reversed data
RandomO(n log n) expectedO(n log n) expectedNo fixed input is always bad
18.02.3

Randomized pivot

Choose the pivot uniformly at random from arr[lo..hi]. No particular input can be consistently bad, because the running time depends on the random choices and not on the data. The expected number of comparisons is about 1.39 · n · log₂ n.

Swap the chosen element to the end, then use Lomuto as usual. Use ThreadLocalRandom in Java, since Math.random() is shared across threads and slower.

Java
1private static int randomizedPartition(int[] arr, int lo, int hi) {
2 int r = lo + ThreadLocalRandom.current().nextInt(hi - lo + 1);
3 swap(arr, r, hi); // move the random pivot to the end
4 return partition(arr, lo, hi); // Lomuto, pivot = arr[hi]
5}
18.02.4

Three-way partition

With many equal keys, two-way partitioning keeps recursing on elements that are already equal to the pivot. Three-way (Dutch national flag) partitioning splits into < pivot, = pivot and > pivot. The equal band is finished immediately and is never sorted again.

Maintain four regions with three indices: [lo..lt-1] < pivot, [lt..i-1] = pivot, [i..gt] unexamined and [gt+1..hi] > pivot. When arr[i] > pivot swap it with arr[gt] and decrement gt, but do not advance i, because the value swapped in has not been examined yet. An array of all equal keys is sorted in one pass: run “All equal”.

Best

O(n)

Average

O(n log n)

Worst

O(n²)

Space

O(log n) stack

Three-way quick sort (Dutch national flag)

Interactive · edit the input

Your input

Try

int[] arr · length 8

4a
2a
4b
4c
1
4d
2b
4e
01234567
< pivot= pivot (final)> pivotBeing comparedDoneOutside this callaEqual keys in original order
Setup

Three-way partitioning groups every key equal to the pivot together, so duplicates are never sorted again.

Step 1 / 55
Java
1public static void quickSort3(int[] arr, int lo, int hi) {
2 if (lo >= hi) return;
3 int pivot = arr[lo + (hi - lo) / 2];
4 int lt = lo, i = lo, gt = hi;
5 while (i <= gt) {
6 if (arr[i] < pivot) swap(arr, lt++, i++);
7 else if (arr[i] > pivot) swap(arr, i, gt--);
8 else i++;
9 }
10 quickSort3(arr, lo, lt - 1);
11 quickSort3(arr, gt + 1, hi);
12}

Live state

0 comparisons0 swaps

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 55 steps
StepLineStateWhat happens
1L1n=8Three-way partitioning groups every key equal to the pivot together, so duplicates are never sorted again.
2L1lo=0 hi=7Enter quickSort3(arr, 0, 7).
3L4pivot=4 lt=0 i=0 gt=7pivot = 4. Invariant: [lo..lt-1] < pivot, [lt..i-1] = pivot, [i..gt] unexamined, [gt+1..hi] > pivot.
4L6pivot=4 lt=0 i=0 gt=7 arr[i]=4Not smaller, check whether it is larger.
5L7pivot=4 lt=0 i=0 gt=7 arr[i]=4Equal to the pivot: leave it in the middle band and advance i.
6L8pivot=4 lt=0 i=1 gt=7i = 1. The equal band grew by one.
7L6pivot=4 lt=0 i=1 gt=7 arr[i]=2Smaller: swap it into the low region.
8L6pivot=4 lt=1 i=2 gt=7lt = 1, i = 2. The low region grew by one.
9L6pivot=4 lt=1 i=2 gt=7 arr[i]=4Not smaller, check whether it is larger.
10L7pivot=4 lt=1 i=2 gt=7 arr[i]=4Equal to the pivot: leave it in the middle band and advance i.
11L8pivot=4 lt=1 i=3 gt=7i = 3. The equal band grew by one.
12L6pivot=4 lt=1 i=3 gt=7 arr[i]=4Not smaller, check whether it is larger.
13L7pivot=4 lt=1 i=3 gt=7 arr[i]=4Equal to the pivot: leave it in the middle band and advance i.
14L8pivot=4 lt=1 i=4 gt=7i = 4. The equal band grew by one.
15L6pivot=4 lt=1 i=4 gt=7 arr[i]=1Smaller: swap it into the low region.
16L6pivot=4 lt=2 i=5 gt=7lt = 2, i = 5. The low region grew by one.
17L6pivot=4 lt=2 i=5 gt=7 arr[i]=4Not smaller, check whether it is larger.
18L7pivot=4 lt=2 i=5 gt=7 arr[i]=4Equal to the pivot: leave it in the middle band and advance i.
19L8pivot=4 lt=2 i=6 gt=7i = 6. The equal band grew by one.
20L6pivot=4 lt=2 i=6 gt=7 arr[i]=2Smaller: swap it into the low region.
21L6pivot=4 lt=3 i=7 gt=7lt = 3, i = 7. The low region grew by one.
22L6pivot=4 lt=3 i=7 gt=7 arr[i]=4Not smaller, check whether it is larger.
23L7pivot=4 lt=3 i=7 gt=7 arr[i]=4Equal to the pivot: leave it in the middle band and advance i.
24L8pivot=4 lt=3 i=8 gt=7i = 8. The equal band grew by one.
25L9pivot=4 lt=3 gt=7Partition done. All 5 elements equal to 4 are in their final place [3..7]. Recurse only on the strictly smaller and strictly larger parts.
26L10lo=0 hi=2Enter quickSort3(arr, 0, 2).
27L4pivot=1 lt=0 i=0 gt=2pivot = 1. Invariant: [lo..lt-1] < pivot, [lt..i-1] = pivot, [i..gt] unexamined, [gt+1..hi] > pivot.
28L6pivot=1 lt=0 i=0 gt=2 arr[i]=2Not smaller, check whether it is larger.
29L7pivot=1 lt=0 i=0 gt=2 arr[i]=2Larger: swap it to the high region. Do not advance i, because the value swapped in is unexamined.
30L7pivot=1 lt=0 i=0 gt=1gt = 1. The high region grew by one.
31L6pivot=1 lt=0 i=0 gt=1 arr[i]=2Not smaller, check whether it is larger.
32L7pivot=1 lt=0 i=0 gt=1 arr[i]=2Larger: swap it to the high region. Do not advance i, because the value swapped in is unexamined.
33L7pivot=1 lt=0 i=0 gt=0gt = 0. The high region grew by one.
34L6pivot=1 lt=0 i=0 gt=0 arr[i]=1Not smaller, check whether it is larger.
35L7pivot=1 lt=0 i=0 gt=0 arr[i]=1Equal to the pivot: leave it in the middle band and advance i.
36L8pivot=1 lt=0 i=1 gt=0i = 1. The equal band grew by one.
37L9pivot=1 lt=0 gt=0Partition done. All 1 element equal to 1 are in their final place [0..0]. Recurse only on the strictly smaller and strictly larger parts.
38L10lo=0 hi=-1Enter quickSort3(arr, 0, -1).
39L2lo=0 hi=-1Zero or one element: done.
40L11lo=1 hi=2Enter quickSort3(arr, 1, 2).
41L4pivot=2 lt=1 i=1 gt=2pivot = 2. Invariant: [lo..lt-1] < pivot, [lt..i-1] = pivot, [i..gt] unexamined, [gt+1..hi] > pivot.
42L6pivot=2 lt=1 i=1 gt=2 arr[i]=2Not smaller, check whether it is larger.
43L7pivot=2 lt=1 i=1 gt=2 arr[i]=2Equal to the pivot: leave it in the middle band and advance i.
44L8pivot=2 lt=1 i=2 gt=2i = 2. The equal band grew by one.
45L6pivot=2 lt=1 i=2 gt=2 arr[i]=2Not smaller, check whether it is larger.
46L7pivot=2 lt=1 i=2 gt=2 arr[i]=2Equal to the pivot: leave it in the middle band and advance i.
47L8pivot=2 lt=1 i=3 gt=2i = 3. The equal band grew by one.
48L9pivot=2 lt=1 gt=2Partition done. All 2 elements equal to 2 are in their final place [1..2]. Recurse only on the strictly smaller and strictly larger parts.
49L10lo=1 hi=0Enter quickSort3(arr, 1, 0).
50L2lo=1 hi=0Zero or one element: done.
51L11lo=3 hi=2Enter quickSort3(arr, 3, 2).
52L2lo=3 hi=2Zero or one element: done.
53L11lo=8 hi=7Enter quickSort3(arr, 8, 7).
54L2lo=8 hi=7Zero or one element: done.
55L12Sorted with 23 comparisons and 5 swaps.
18.02.5

Worst-case O(n²)

The worst case happens when every partition is as lopsided as possible: sizes 0 and n - 1. The work is (n - 1) + (n - 2) + … + 1 = n(n - 1)/2 comparisons, and the recursion depth reaches n, which can overflow the stack on large arrays.

Typical triggers are sorted or reverse sorted input with a first or last pivot, and all-equal input with two-way partitioning. Production sorts defend against it with median-of-three or random pivots, three-way partitioning, and a fallback to heap sort once recursion gets too deep (introsort, see lesson 18.07).

18.02.6

Tail recursion

Quick sort makes two recursive calls, and the second is a tail call. Java does not eliminate tail calls, so instead recurse into the smaller side and loop on the larger side. The smaller side is at most half the size, so the stack depth is at most log₂ n even when the partitions are badly unbalanced.

This bounds stack space to O(log n) in the worst case, although time may still be O(n²).

Java
1static void quickSort(int[] arr, int lo, int hi) {
2 while (lo < hi) {
3 int p = partition(arr, lo, hi);
4 if (p - lo < hi - p) { // left side is smaller: recurse into it
5 quickSort(arr, lo, p - 1);
6 lo = p + 1; // then loop on the larger right side
7 } else {
8 quickSort(arr, p + 1, hi);
9 hi = p - 1;
10 }
11 }
12}

Worst

O(n²) time

Space

O(log n) stack guaranteed

Practice ladder

Now try it yourself

Easy

  • Partition an array around a pivot (Lomuto)
  • Sort colors (Dutch national flag, values 0/1/2)
  • Move all zeros to the end

Medium

  • Implement quick sort with a random pivot
  • Count the comparisons quick sort makes on sorted input of size n
  • Sort an array with many duplicates using three-way partitioning

Advanced

  • Introsort: switch to heap sort at depth 2·log₂ n
  • Dual-pivot quick sort partitioning
  • Prove the expected 2n·ln n comparison count

Key takeaways

  • Quick sort is O(n log n) on average but O(n²) in the worst case, and it is not stable.
  • Pivot choice decides everything: median-of-three or random pivots avoid the usual bad inputs.
  • Three-way partitioning makes duplicates cheap; recursing into the smaller side bounds stack depth.