Quickselect runs in expected O(n) — why isn't it the default way to pick the k smallest?
answer
- What does the word 'expected' actually cover?
- Recursing into one side, then one side again
- Which inputs make the split degenerate?
- What state does the caller's buffer end in?
- It needs everything resident and mutable
basics
~20 sExpected O(n) is an average over pivot choices, not a per-call guarantee: naive pivot rules degrade to O(n^2) on sorted or duplicate-heavy input. Quickselect also permutes the caller's data in place and needs the entire input resident.
solid answer
~50 sThree reasons, and the complexity is only the first. One: `O(n)` there is *expected*, meaning averaged over pivot outcomes — a single call has no linear guarantee, and with a naive pivot rule such as always taking the first element, already-sorted or heavily duplicated input drives it to O(n^2). Two: it works by permuting the buffer in place, so if the caller's ordering matters you must copy the input first, and that copy is O(n) time and O(n) space — most of the advantage over a one-pass candidate heap evaporates. Three: it is strictly offline. It needs the whole input addressable and makes several passes over shrinking ranges, so it cannot answer a top-k question about data that arrives as a stream or does not fit. Quickselect wins when the data is already in a buffer you own, it fits, and you need the *set* rather than a ranking.
code
pseudocode · 11 linesselect(a, lo, hi, k): // k-th smallest of a[lo..hi], 0-based rank
while lo < hi:
p = partition(a, lo, hi) // splits a around one pivot, returns pivot's final index
if k == p:
return a[k]
if k < p:
hi = p - 1
else:
lo = p + 1
return a[lo]
// note: a is rearranged as a side effect; only one side is ever revisitedgo deeper
Remember two facts and you can hold the conversation: quickselect's O(n) is an average, and its worst case is O(n^2). Also remember it changes the order of the data it is given.
Explain the cost geometry out loud — n + n/2 + n/4 sums to about 2n when splits are balanced, and n + (n-1) + (n-2) when they are not — and name the inputs that produce each.
Show you weigh preconditions, not just bounds: residency, mutability of the caller's buffer, and whether a quadratic tail is acceptable inside a per-request latency budget versus an offline batch.
Frame it as a risk decision: an expected-linear method with a quadratic tail against a bounded O(n log k) method, judged against the SLA you actually publish and what a bad day costs the business.
## What the fragment shows The attached pseudocode is quickselect's skeleton. Partitioning is treated as a black box: it splits the current range around one pivot and reports where that pivot finally sits. The interesting part is what happens next — unlike a sort, the routine recurses into **one** side only, because rank `k` can lie in only one of them. ## Where expected O(n) comes from Suppose each partition splits the range roughly in half. The first pass touches `n` elements, the next about `n/2`, then `n/4`, and so on. That geometric series sums to about `2n`, so the total work is linear — no logarithmic factor, because the discarded side is never revisited. That is the whole trick, and it is genuinely a different order of work from a sort. ## Why that is a promise about averages, not about your call Now suppose the pivot lands at the very edge every time: the ranges shrink by one element per pass. The work is `n + (n-1) + (n-2) + ...`, which is O(n^2). With a naive rule like "pivot on the first element," already-sorted input produces exactly that, and input dominated by a handful of repeated key values can behave similarly depending on how the partition handles equal keys. Cheapest catalog prices are, notoriously, both: exports often arrive already ordered, and prices repeat heavily. This is the precise sense in which "expected O(n)" differs from "O(n)". An expected bound averages over the randomness in the algorithm or an assumed input distribution. It is also *not* an amortized bound: amortized bounds cover the total cost of a worst-case **sequence** of operations, and would still guarantee something about the aggregate. Expected guarantees nothing about any individual call — which is exactly what a tail-latency budget cares about. ## The second cost: it eats the input Quickselect achieves O(1) auxiliary space by rearranging the buffer it is given. After the call, the caller's records are in an arbitrary order, and the only structure left is that everything before position `k` is no larger than everything after it. Two consequences: - If some other part of the system depends on the original order — an insertion order, a previously sorted state, a shared cache of records — you must copy the input first, at O(n) time and O(n) space. That copy alone costs as much as the selection. - The result is an unordered prefix. The k cheapest come out as a group, not as a ranking, so a display that wants "cheapest first" needs a further O(k log k) sort of that prefix. A retained-candidate heap sits at the other end of this axis: O(k) extra space, but the input is read-only. ## The third cost: it is offline Quickselect needs random access to the whole range and revisits shrinking sub-ranges repeatedly. That rules out three real situations at once: input that arrives incrementally and must be answered about at any moment; input too large to hold; and input exposed only as a forward, one-shot sequence. A one-pass candidate scan handles all three; a full sort handles none of them in memory either, though external variants exist. ## When quickselect is the right call It is a strong choice — the objection is to it being the *default*, not to the algorithm: - The data is already materialized in a buffer you own and may disturb. - You want the set of k, not a ranking. - `k` is a meaningful fraction of `n`, where the candidate heap's `log k` stops being small and quickselect's linearity actually shows up. - The call is one-shot and offline, so a rare bad case costs a slow batch rather than a violated request SLA. And the mirror image: prefer the scanning heap when input streams or must not be touched; prefer the full sort when ranking is required anyway, or when n is small enough that constants and clarity dominate the asymptotics. ## The misconception to name explicitly "Quickselect is O(n), so it always beats O(n log k) and O(n log n)" fails on three fronts at once: the bound is expected rather than worst-case, the constant factors and memory-access patterns of a partitioning pass are not free, and the algorithm imposes preconditions — full residency, random access, permission to mutate — that the alternatives do not. Comparing algorithms by their headline complexity alone, without their preconditions and side effects, is the single most common way this question is failed.
- How is 'expected O(n)' different from 'amortized O(n)'?Amortized bounds the total cost of a worst-case sequence of operations, so the aggregate is guaranteed even though one operation may be slow. Expected averages over randomness or an assumed input distribution and guarantees nothing about any particular run — an adversarial or simply unlucky input can be quadratic. Amortized survives an adversary; expected does not.
- The catalog export arrives already sorted by price. What does that do to each of the three approaches?It is the worst case for quickselect under a naive edge pivot, turning expected linear into quadratic. It is harmless for the candidate-retaining scan, which still costs O(n log k) — and in fact cheap, since after the first k records nothing displaces anything. And an adaptive merge-based sort exploits it, running close to O(n) on already-ordered runs, which can make simply sorting the fastest of the three on that input.
- You are told the input buffer is shared and must survive the call. What does quickselect now cost?An O(n) copy in time and O(n) in space before it can start, plus its own expected O(n) pass. That puts its memory at the level of a full sort while keeping the O(n^2) tail, so a read-only single-pass scan with O(k) state usually becomes the better trade unless k is large.
Expected O(n) is like an average commute time: it says nothing reassuring about the morning the road is closed, and a service promise is written against the bad morning.
saying these in an interview costs you the question
- Saying quickselect is O(n) with no worst case
- Treating expected time as a per-call guarantee
- Confusing expected time with amortized time
- Forgetting that quickselect permutes the caller's data
- Claiming the k smallest come out sorted
- Proposing quickselect for data that arrives incrementally