Chapter 19: Selection, Order Statistics and Ranking
19.02Beginner / Intermediate5 topics16 min1 interactive animation

Quickselect

Quickselect finds the k-th smallest element by partitioning like quick sort, but recursing into only one side. That halves the expected work at every level and gives expected O(n) time.

Learning goals

  • Trace quickselect and decide which side to continue on
  • Explain why a random pivot gives expected linear time
  • Describe its quadratic worst case and when it occurs
  • Handle duplicate keys with three-way partitioning

Prerequisites

Quick sort partitioningExpected value (intuition)

To find the 5th shortest person in a room, split everyone around one person. If the pivot has exactly four shorter people, they are the answer. Otherwise you only need to keep looking among one group, which is smaller each time.

19.02.1

Partition-based selection

Partition the array around a pivot. The pivot ends at index p, which is its final sorted position. If p == k - 1, the pivot is the answer. If p < k - 1, the answer lies to the right, so continue on [p+1..hi]. Otherwise it lies to the left.

Quick sort recurses on both sides; quickselect recurses on one. In the animation the discarded part of the array fades out, and you can change k to see it follow a different path. It is the fastest option when the whole array is in memory and you can reorder it.

Java
1public static int quickSelect(int[] arr, int k) { // k-th smallest, 1-based
2 int lo = 0, hi = arr.length - 1, idx = k - 1;
3 while (lo <= hi) {
4 int p = partition(arr, lo, hi);
5 if (p == idx) return arr[p];
6 if (p < idx) lo = p + 1;
7 else hi = p - 1;
8 }
9 throw new IllegalArgumentException("k out of range");
10}

Best

O(n)

Average

O(n)

Worst

O(n²)

Space

O(1)

Quickselect (k-th smallest)

Interactive · edit the input

Your input

Try

int[] arr · length 8

7
10
4
3
20
15
8
1
01234567
PivotBeing comparedLess than pivotThe answerDiscarded
Setup

The 3rd smallest value ends at sorted index 2. Partition once, then only follow the side that contains index 2.

Step 1 / 35
Java
1public static int quickSelect(int[] arr, int k) { // k-th smallest, 1-based
2 int lo = 0, hi = arr.length - 1, idx = k - 1;
3 while (lo <= hi) {
4 int p = partition(arr, lo, hi); // Lomuto, pivot = arr[hi]
5 if (p == idx) return arr[p];
6 if (p < idx) lo = p + 1; // answer is on the right
7 else hi = p - 1; // answer is on the left
8 }
9 return -1;
10}

Live state

0 comparisons0 swaps

k

3

idx

2

lo

0

hi

7

No condition evaluated on this step
Execution trace (dry run table)Show 35 steps
StepLineStateWhat happens
1L2k=3 idx=2 lo=0 hi=7The 3rd smallest value ends at sorted index 2. Partition once, then only follow the side that contains index 2.
2L3lo=0 hi=7 idx=2Window [0..7] still contains index 2.
3L4pivot=1 i=0 lo=0 hi=7Partition with pivot 1 (the last element).
4L4pivot=1 i=0 j=0Not smaller than the pivot.
5L4pivot=1 i=0 j=1Not smaller than the pivot.
6L4pivot=1 i=0 j=2Not smaller than the pivot.
7L4pivot=1 i=0 j=3Not smaller than the pivot.
8L4pivot=1 i=0 j=4Not smaller than the pivot.
9L4pivot=1 i=0 j=5Not smaller than the pivot.
10L4pivot=1 i=0 j=6Not smaller than the pivot.
11L4p=0 pivot=1 idx=2The pivot lands at its final index p = 0.
12L5p=0 idx=2p < idx: the answer is to the right of the pivot, so discard [0..0].
13L6lo=1 hi=7lo = 1.
14L3lo=1 hi=7 idx=2Window [1..7] still contains index 2.
15L4pivot=7 i=1 lo=1 hi=7Partition with pivot 7 (the last element).
16L4pivot=7 i=1 j=1Not smaller than the pivot.
17L4pivot=7 i=1 j=2Smaller than the pivot: move it into the low region.
18L4pivot=7 i=2 j=3Smaller than the pivot: move it into the low region.
19L4pivot=7 i=3 j=4Not smaller than the pivot.
20L4pivot=7 i=3 j=5Not smaller than the pivot.
21L4pivot=7 i=3 j=6Not smaller than the pivot.
22L4p=3 pivot=7 idx=2The pivot lands at its final index p = 3.
23L5p=3 idx=2p > idx: the answer is to the left of the pivot, so discard [3..7].
24L7lo=1 hi=2hi = 2.
25L3lo=1 hi=2 idx=2Window [1..2] still contains index 2.
26L4pivot=3 i=1 lo=1 hi=2Partition with pivot 3 (the last element).
27L4pivot=3 i=1 j=1Not smaller than the pivot.
28L4p=1 pivot=3 idx=2The pivot lands at its final index p = 1.
29L5p=1 idx=2p < idx: the answer is to the right of the pivot, so discard [1..1].
30L6lo=2 hi=2lo = 2.
31L3lo=2 hi=2 idx=2Window [2..2] still contains index 2.
32L4pivot=4 i=2 lo=2 hi=2Partition with pivot 4 (the last element).
33L4p=2 pivot=4 idx=2The pivot lands at its final index p = 2.
34L5p=2 idx=2p == idx, so arr[2] = 4 is the answer. The rest of the array never needed sorting.
35L5return=4return 4 after 14 comparisons, which is less work than sorting the whole array.
19.02.2

Randomized pivots

With a fixed pivot rule, an adversarial input (such as sorted data and a last-element pivot) forces bad splits. Picking the pivot at random makes the running time independent of the input order.

Swap a randomly chosen element to the end before partitioning. Median-of-three is a cheaper heuristic that works well on sorted and reverse sorted data but is not random.

Java
1int r = lo + ThreadLocalRandom.current().nextInt(hi - lo + 1);
2swap(arr, r, hi); // move the random pivot to the end
3int p = partition(arr, lo, hi);
19.02.3

Expected linear time

Call a pivot good if it lands in the middle half of the sorted order (between the 25th and 75th percentile). That happens with probability 1/2. After a good pivot, at most 3/4 of the elements remain. In expectation two partitions are needed to get one good one.

So the expected work is E[T(n)] ≤ 2n + E[T(3n/4)]. Unrolling gives 2n · (1 + 3/4 + (3/4)² + …) = 2n / (1 - 3/4) = 8n = O(n). The partitioning cost shrinks geometrically, unlike quick sort, where both sides stay.

AlgorithmRecurses onExpected comparisons
Quick sortBoth sidesabout 1.39 n log₂ n
QuickselectOne sideat most about 3.4 n (for the median)
19.02.4

Worst-case behavior

If every pivot is the minimum or the maximum, each round removes only one element, giving n + (n - 1) + … + 1 = O(n²). With a random pivot this has vanishingly small probability. With a fixed pivot rule it is easy to trigger.

Introselect protects against this: it counts rounds and switches to the median-of-medians algorithm (next lesson) when progress is too slow, so the worst case becomes O(n). C++ std::nth_element does this.

19.02.5

Duplicate keys

Lomuto partitioning puts values equal to the pivot on the right side. When all elements are equal, every partition removes only one element and the algorithm is quadratic. Run the “All equal (quadratic)” preset to see it.

Use three-way partitioning (< pivot, = pivot, > pivot). If the target index falls inside the equal band, the answer is the pivot value and the search ends immediately. Otherwise continue on the strictly smaller or strictly larger part.

Java
1// three-way quickselect step: [lo..lt-1] < pivot, [lt..gt] == pivot, [gt+1..hi] > pivot
2if (idx < lt) hi = lt - 1;
3else if (idx > gt) lo = gt + 1;
4else return pivot; // idx is inside the equal band: done

Practice ladder

Now try it yourself

Easy

  • Kth largest element in an array (convert to the k-th smallest)
  • Find the median of an odd-length array

Medium

  • Implement quickselect with a random pivot
  • Top K frequent elements with quickselect on counts
  • Kth smallest in a stream of 10 elements by hand

Advanced

  • Three-way quickselect for arrays with many duplicates
  • Prove quickselect's expected O(n) bound
  • Introselect with a depth counter and fallback

Key takeaways

  • Quickselect partitions once per round and keeps only the side containing the target index.
  • Expected O(n) with a random pivot; worst case O(n²) with bad pivots or many duplicates.
  • Three-way partitioning and an introselect fallback remove both weaknesses.