skip to content

Quicksort blows the stack on a year of already-ordered sensor timestamps — how do you bound its recursion depth?

level: seniorimportance: should knowfreq 40%

answer

  1. the volume is not the problem, the shape is
  2. which of the two recursive calls is in tail position?
  3. one side is always at most half
  4. keep the frame for the small side, loop the big one
  5. this bounds space, not comparisons

basics

~20 s

Ordered input makes every split lopsided, so the recursion holds one frame per record. Recurse into the smaller partition and loop on the larger — the smaller side is at most half, capping depth at log n.

solid answer

~50 s

Diagnose it as a split-shape problem, not a data-volume problem. Timestamps arriving in recording order make every partition lopsided, so the recursion becomes a chain roughly as long as the input and the stack goes before the sort finishes. The structural fix costs nothing: after partitioning, recurse into the *smaller* of the two sides and continue the larger one in a loop in the current frame, replacing the second recursive call with an update of the range bounds. The smaller side is at most half the remaining range by definition, so each nested frame at least halves the problem and the depth is bounded at about log2 n — around twenty frames for half a million records — no matter how lopsided the splits are. Be precise about what this buys: it bounds *space* only. The comparison count on that input is still quadratic, which is a separate fix.

code

pseudocode · 9 lines
pseudocode
QUICKSORT(a, lo, hi):
  while lo < hi:
    p = PARTITION(a, lo, hi)   // ... p = final index of the pivot
    if p - lo < hi - p:
      QUICKSORT(a, lo, p - 1)  // recurse on the smaller left side
      lo = p + 1               // loop on the larger right side
    else:
      QUICKSORT(a, p + 1, hi)  // recurse on the smaller right side
      hi = p - 1               // loop on the larger left side

go deeper

for a junior

Recall that ordered input can make a sort recurse once per element and that the recursion stack is finite memory, so a deep recursion crashes the process.

for a middle

Explain the mechanism of the fix: the second recursive call is in tail position, and always keeping the frame for the smaller side means each frame at least halves the range.

for a senior

Diagnose from the symptom to the split shape, apply the bound, and state clearly that it addresses space and leaves the quadratic comparison count untouched.

for a principal

Own the choice between bounding depth, capping depth with a fallback algorithm, and picking a sort whose bound is structural, and justify it against the job's failure tolerance and maintenance cost.

### Read the failure correctly The symptom is a stack overflow; the cause is the split ratio. Sensor timestamps appended in recording order arrive already ordered, and a pivot taken from a fixed end of the range is then the smallest or largest element present. Every partition therefore yields an empty side and a side of n-1, and the recursion is a chain with one frame per record — about 525,600 frames for a year of per-minute samples, against a typical stack that holds tens of thousands. The process dies long before the quadratic comparison count would have made itself felt. Two diagnoses that miss: "the dataset got too big" (a balanced run of the same size needs 20 frames, not half a million) and "raise the stack limit" (this moves the cliff by a constant factor while the depth still grows linearly with input size — next year's data hits it again). ### The structural fix: recurse small, loop large Quicksort's second recursive call is in tail position — nothing happens after it returns. A tail call can be replaced by updating the current frame's bounds and looping. If you always eliminate the call on the *larger* side and keep the recursive call for the smaller side, you get a guarantee: > The smaller partition is at most half the range, because two parts cannot both exceed half. So each nested frame handles at most half of what its parent handled, and the depth is at most log2 n. That bound holds for *every* input, including the maximally lopsided one, because it never assumes anything about where the pivot lands — it only uses the fact that of two pieces, the smaller one cannot be the bigger half. Half a million records now need at most about 20 frames instead of half a million. The cost of the fix is one comparison of the two side lengths per partition; the comparison count and the work done are otherwise identical. ### The direction of the claim This is the point interviewers actually probe. Bounding recursion depth fixes **space**, not **time**. The same ordered input still produces ranges of sizes n, n-1, n-2, … and therefore still does O(n^2) comparisons; you have converted a crash into a run that finishes eventually. A candidate who says "recursing on the smaller side makes quicksort O(n log n)" has swapped the two axes. Time is repaired by changing *where the split lands* — which is a different lever entirely — while depth is repaired by changing *which side keeps the frame*. ### What mainstream implementations actually do Different runtimes made visibly different calls on the same problem, which is a good sign that there is no single right answer. C++ standard libraries settled on introsort, which counts recursion depth and, past a threshold of about 2·log2 n, abandons the quicksort recursion for heapsort — accepting a slower constant in exchange for a hard O(n log n) worst case with bounded depth. Java's library sorts split by data kind instead, using a dual-pivot quicksort variant for primitives and a stable merge-based sort for objects, so the object path has logarithmic depth by construction. The smaller-side trick, a depth cap with a fallback, and choosing an algorithm whose depth is structural are three answers to the same question, and a senior candidate should be able to say which one a given constraint argues for. ### Checking your reasoning at the whiteboard A quick way to convince an interviewer the bound is real: if the smaller side has size s and the range has size n, then s ≤ (n-1)/2 < n/2. Applying that at every nested frame gives sizes below n/2, n/4, n/8, so the chain of frames cannot exceed log2 n before reaching size 1. Nothing in the argument mentions the pivot, the data, or the ordering, which is exactly why the guarantee is unconditional — the same style of reasoning as merge sort's, which gets its depth bound from splitting positionally. ### The rest of the hardening conversation Bounding depth is the first move; the interviewer's follow-ups usually go to whether you would also cap depth and switch algorithms, whether you would prefer a sort whose worst case is structural when the job runs unattended overnight, and how you would have caught this before production — a test that feeds the sort a sorted range is the one that would have found it, and sorted input is not an exotic case for timestamped data, it is the normal case.

  • Does recursing into the smaller side also fix the quadratic running time?
    No. It bounds stack depth at O(log n) and nothing else. On the same ordered input the partition sizes still run n, n-1, n-2 and so on, so the comparison count is still O(n^2) — the job now survives instead of crashing, and takes far too long. Time is a separate lever: it depends on where the split lands, not on which side keeps the frame.
  • Why does recursing on the smaller partition cap the depth at about log2 n?
    Because of the two pieces the partition produces, the smaller one cannot exceed half the range — if both exceeded half their sizes would sum to more than the range. So each nested frame handles at most half of its parent's range, and repeated halving reaches size one in at most log2 n steps. The argument never mentions the pivot, which is why the bound holds for every input.
  • Merge sort already has O(log n) depth by construction — is that the simpler answer here?
    It removes this failure mode entirely, since its depth is fixed by positional splitting and no input can change it, and its worst-case time is O(n log n) rather than quadratic. Whether it is the right default depends on costs outside the recursion structure. What you should not do is claim quicksort is unusable: depth is cheap to bound, and the choice deserves an argument rather than a reflex.

saying these in an interview costs you the question

  • Says the smaller-side trick makes quicksort O(n log n) in time
  • Blames the volume of data rather than the split shape
  • Proposes raising the stack limit as the fix
  • Claims recursion depth does not count toward space complexity
  • Cannot explain why the smaller side is at most half

context