Quant Memo
Core

Quickselect and Top-K Selection

Quickselect finds the k-th smallest element of a list in average linear time by reusing quicksort's partitioning trick but only recursing into the one side that matters.

Prerequisites: Big-O Complexity, Recursion and the Call Stack

Finding the median of a million numbers doesn't require sorting all million of them — full sorting throws away the fact that you only wanted one specific position in the order, not the whole order. Quickselect finds the kk-th smallest element directly, in average O(n)O(n) time, by borrowing quicksort's partition step but discarding the half of the work a full sort would do.

The idea: partition, then recurse into only one side

Quicksort partitions an array around a pivot — everything smaller goes left, everything larger goes right — then recursively sorts both sides. Quickselect partitions the same way, but after partitioning it knows exactly which side the kk-th element must be in (by comparing kk to the pivot's final position), so it recurses into only that one side and ignores the other completely.

That single change is what drops the average complexity from quicksort's O(nlogn)O(n \log n) to quickselect's O(n)O(n): each partition step costs O(n)O(n), but the next call operates on roughly half the data, giving n+n/2+n/4+2n=O(n)n + n/2 + n/4 + \dots \approx 2n = O(n) — a geometric series that sums to a constant multiple of nn, instead of quicksort's logn\log n full passes over shrinking-but-still-both-sides subarrays.

Worked example: 3rd smallest of [7, 2, 9, 4, 1, 8]

Looking for k=3k=3 (1-indexed), pivot on the last element, 8:

  • Partition around 8: everything is smaller (8 is the max), so it lands at index 5. Values left of it: [7, 2, 9, 4, 1] in some order — index 5 means 8 is the 6th smallest. Since 3<63 < 6, recurse into the left part only, discarding 8 and the right side entirely (here, nothing).
  • New array [7, 2, 9, 4, 1], still looking for the 3rd smallest overall. Pivot on 1 (last element): everything else is larger, so 1 lands at index 0 — the 1st smallest. Since 3>13 > 1, recurse into the right part, now looking for the 31=23-1=2nd smallest of [7, 2, 9, 4].
  • Pivot on 4 (last element): [2] smaller, [7, 9] larger, so 4 lands at index 1 — the 2nd smallest of this subarray. That's exactly k=2k=2 — done. Answer: 4.

Check by full sort: [1, 2, 4, 7, 8, 9] — the 3rd smallest is indeed 4. Quickselect found it after touching each remaining subarray once, never sorting the discarded half.

import random

def quickselect(arr, k):                 # k-th smallest, 0-indexed, avg O(n)
    pivot = random.choice(arr)
    lows  = [x for x in arr if x < pivot]
    highs = [x for x in arr if x > pivot]
    pivots = [x for x in arr if x == pivot]

    if k < len(lows):
        return quickselect(lows, k)
    elif k < len(lows) + len(pivots):
        return pivot                      # k lands inside the pivot block
    else:
        return quickselect(highs, k - len(lows) - len(pivots))
quicksort: both sides n quickselect: one side only n discarded
Quicksort's total work doubles back on itself across both halves at every level; quickselect throws one half away every time, which is why its average cost is linear instead of n log n.

Quickselect is average O(n)O(n), not worst-case — a consistently bad pivot choice (always the min or max) degrades to O(n2)O(n^2), exactly like quicksort's worst case. A randomized pivot, as above, makes the bad case exponentially unlikely rather than eliminating it; the deterministic median-of-medians variant guarantees worst-case O(n)O(n) at a higher constant-factor cost.

Where it shows up

Interviewers ask for "kth largest element," "top k frequent elements," or median-finding, and the expected answer contrasts quickselect's average O(n)O(n) against sorting's O(nlogn)O(n \log n) or a heap's O(nlogk)O(n \log k) — and knowing which to reach for depending on whether kk is small or you need the result repeatedly. In quant work, this is the algorithm behind picking the top-decile names in a cross-sectional signal, finding the median of a rolling window without a full sort, or computing a VaR percentile from a large simulated P&L distribution without sorting the whole array first.

Related concepts

Practice in interviews

Further reading

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 9)
ShareTwitterLinkedIn