Coding Interview Patterns Module 7 – Sorting as a tool
Quickselect for the k-th element
Find the k-th smallest value, such as the median, in expected linear time: random pivots, three-way partitions, and when a library call or a sort wins.
What you will learn
- Find the k-th smallest element in expected O(n) time with quickselect
- Use random pivots and a three-way partition to survive sorted input and repeats
- Argue why the expected work is linear, and when it degrades to quadratic
- Choose between quickselect, a sort, a heap and library calls such as std::nth_element
Before you start
What you’ll learn · Before you start
What you will learn
- Find the k-th smallest element in expected O(n) time with quickselect
- Use random pivots and a three-way partition to survive sorted input and repeats
- Argue why the expected work is linear, and when it degrades to quadratic
- Choose between quickselect, a sort, a heap and library calls such as std::nth_element
Before you start
On this page
Quickselect finds the k-th smallest value of an unsorted array, such as its median, in expected O(n) time and without sorting it. It partitions the array around a pivot the way quicksort does, which leaves the pivot at the position it would have in sorted order, and then keeps only the side that holds position k: the other side is never looked at again. C. A. R. Hoare published it as FIND, Algorithm 65 of the Communications of the ACM. A random pivot keeps the expected work linear on every input, and a three-way partition keeps repeated values cheap. This lesson’s problem asks for the median income of a large survey.
The problem: the median household income
The problem
A household survey records the monthly income of n households in rupees, income[0..n − 1], in the order the
forms were entered. Return the k-th smallest income, counting k from 1, so that k = (n + 1) / 2 gives the median
of an odd-sized survey. Limits: 1 ≤ n ≤ 10⁶, 1 ≤ k ≤ n and 0 ≤ income[i] ≤ 10⁹; incomes repeat often, and the
answer should take expected O(n) time.
[42000, 18000, 95000, 27000, 61000]with k = 3 gives 42000, the middle of the five.[30000, 30000, 30000, 12000]with k = 2 gives 30000: repeats count, so the second smallest is a copy of 30000.[7]with k = 1 gives 7.
Selection asks for one position of the sorted order
Text description of the diagram
The top row shows the survey as entered: 42,000, 18,000, 95,000, 27,000 and 61,000; the box with 42,000 is drawn thicker.
The bottom row shows the same incomes in sorted order, numbered 1 to 5: 18,000, 27,000, 42,000, 61,000 and 95,000. Only position 3, 42,000, is highlighted; the other four are greyed out, because the question never needs them in order. The line under the rows says that k = 3 asks for one position only: 42,000.
The brute force: take out the minimum k times
Remove the smallest value, k times, and the last one removed is the answer. Each removal scans what is left, so the median of n values costs about 3n²/8 steps. Sorting the whole survey and reading one position is the usual shortcut, and the run counts what it pays for:
# The k-th smallest income by brute force: take out the smallest value k times. Then the cost of sorting instead.
import random
def kth_by_repeated_minimum(values, k):
"""Removes the minimum k times; returns the last one removed and the number of values looked at."""
rest = list(values)
looked = 0
for _ in range(k):
looked += len(rest)
smallest = min(rest)
rest.remove(smallest)
return smallest, looked
for incomes, k in (([42000, 18000, 95000, 27000, 61000], 3), ([30000, 30000, 30000, 12000], 2), ([7], 1)):
print(f"{incomes}, k = {k} -> {kth_by_repeated_minimum(incomes, k)[0]}")
rng = random.Random(5)
survey = [rng.randint(5_000, 400_000) for _ in range(2_001)]
_, looked = kth_by_repeated_minimum(survey, 1_001)
print(f"median of 2,001 incomes: {looked:,} values looked at")
n = 1_000_001
print(f"median of {n:,} incomes: {(n + (n - 500_000)) * 500_001 // 2:,} values to look at")
class Counted:
"""An income that counts how often sorted() compares it (sorted() only uses <)."""
comparisons = 0
def __init__(self, value):
self.value = value
def __lt__(self, other):
Counted.comparisons += 1
return self.value < other.value
sorted(Counted(rng.randint(5_000, 400_000)) for _ in range(100_001))
print(f"sorting 100,001 incomes to read one of them: {Counted.comparisons:,} comparisons") Output
[42000, 18000, 95000, 27000, 61000], k = 3 -> 42000 [30000, 30000, 30000, 12000], k = 2 -> 30000 [7], k = 1 -> 7 median of 2,001 incomes: 1,502,501 values looked at median of 1,000,001 incomes: 375,001,250,001 values to look at sorting 100,001 incomes to read one of them: 1,531,912 comparisons
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 select_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
For 2,001 incomes the median took 1,502,501 values looked at, and for a million incomes it would take about 3.75 × 10¹¹, far beyond any time limit. Sorting 100,001 incomes made 1,531,912 comparisons, an O(n log n) bill for a question about one position: the order of the other 100,000 incomes was never asked for.
What to notice
Only one position of the sorted order matters. A partition around any pivot puts the pivot exactly where a full sort would, with every smaller value before it and every larger value after it, so after one pass you know on which side the answer lies and can drop the other side whole.
Quickselect, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- The partition invariant, and why the loop always ends
- Why the expected work is linear: a proof in four lines
- A dry run on the first example
- The code in Python, JavaScript, Java, C++ and Go
- Pivots measured: the quadratic case and the average
- Three slips, caught by a stress test
- In Python, measure before you hand-write it
- Guaranteed linear time, and the library calls
- Edge cases and common bugs
- Variants, with answers
- Follow-up questions, answered
- Exercise and practice set
- Select the k-th smallest value without sorting
- Check yourself
- Interview questions
- A service keeps the last 10⁷ request latencies in an int array and must report their median every minute within 200 ms. Compare a sort, a heap and quickselect for the job, with their costs, and say what you would ship.
- Your quickselect passes the sample tests but times out on two hidden tests with n = 10⁵: one input is already sorted, the other repeats a single value. What is wrong with your partition, and what do you change?
- A delivery app must return the 20 stores nearest to a customer out of 50,000 candidates, ordered by distance. Would you use quickselect, a heap or a full sort? What does each cost, and what changes if k becomes 5,000?
- Key takeaways
1 diagram · 5 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 23 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Algorithm 65: find (C. A. R. Hoare, Communications of the ACM 4(7)) (Association for Computing Machinery)
- Time bounds for selection (Blum, Floyd, Pratt, Rivest and Tarjan, Journal of Computer and System Sciences 7(4)) (Elsevier)
- Dictionary of Algorithms and Data Structures: Select (National Institute of Standards and Technology)
- std::nth_element (cppreference.com)
- heapq: heap queue algorithm (nsmallest) (Python Software Foundation)
- random: generate pseudo-random numbers (Python Software Foundation)
- Go standard library: package math/rand/v2 (The Go Authors)
- sys.getrecursionlimit (Python Software Foundation)
- Java SE 25 API: Random (Oracle)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress