skip to content

Recursion & Backtracking

Recursive thinking as its own subject: how recursive calls actually execute, when iteration is the better tool, and how divide-and-conquer and backtracking build on the same machinery. Interviewers probe this because most tree, graph, and combinatorial problems fall apart without a solid recursive mental model.

part ofData structures & algorithmsoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

Merge sort and quicksort are both divide and conquer — which phase does each spend its per-level work in?

level: juniorimportance: must knowfreq 82%

answer

  1. one sort works before recursing, one after
  2. which one needs a pivot placed?
  3. count comparisons in merge sort's split step
  4. when quicksort's calls return, what's left to do?
  5. pre-order work versus post-order work

basics

~20 s

Quicksort spends in the divide step: partitioning is a linear pass, and when the halves return there is nothing to combine. Merge sort splits by index in constant time and pays its linear pass when combining.

solid answer

~40 s

Both fit the same split-solve-combine template, but they load opposite ends of it. Quicksort's divide step partitions the range around a pivot — one linear pass — and the pivot lands in its final position, so when the two recursive calls return the range is already sorted and the combine step is empty. Merge sort's divide step is just an index calculation, O(1) bookkeeping with no comparisons at all; every comparison happens on the way back up, in the linear merge that interleaves two sorted halves. Both therefore do O(n) work per level and, with balanced splits, about log n levels, which is where the shared O(n log n) comes from. The difference that matters is *when* the work happens: quicksort's is pre-order, merge sort's is post-order.

go deeper

for a junior

Be ready to say in one breath that quicksort does its linear work while splitting and merge sort does it while combining. Naming which phase is empty for quicksort is the whole answer at this level.

for a middle

Explain why the placement follows from the mechanism: the pivot lands in its final position, so nothing is left to combine, while two independently sorted halves carry no information about their interleaving.

for a senior

Connect placement to behaviour under real data: a positional split is data-independent, a value-based split is not, so only one of the two has a shape that hostile input can distort.

for a principal

Own the framing that both sorts share an average bound but not a risk profile, and be able to say which structural property you would rely on when a pipeline must meet a bound rather than an average.

### One template, three instances Divide and conquer has three phases: **divide** the input into subproblems, **conquer** them by recursing, and **combine** the sub-results into the answer. Every instance pays for all three phases, but how much cost sits in each phase is what tells the instances apart — and it is the distribution, not the total, that an interviewer is probing when they ask you to compare merge sort and quicksort. Use a concrete workload: a year of readings from a sensor fleet, each tagged with a timestamp, and the job is to put roughly half a million of them in chronological order. ### Quicksort — heavy divide, empty combine Quicksort picks a pivot value and rearranges the range so that everything ordering before the pivot sits to its left and everything after sits to its right. That rearrangement touches every element in the range once, so the divide step costs O(n) for a range of size n. Crucially, the pivot ends up at the index it will occupy in the final sorted order and never moves again. Now recurse on the left part and on the right part. When those two calls return, both sides are sorted, the pivot between them is already correct, and the whole range is in order. There is nothing left to do — the function simply returns. Quicksort's combine step is *empty*. All of its comparison work happened **before** the recursive calls. ### Merge sort — trivial divide, heavy combine Merge sort divides by position: compute the midpoint of the index range and hand each half to a recursive call. No values are compared, nothing is moved; the divide step is O(1) arithmetic per call. When the two calls return, you hold two sorted sequences and no information about how they interleave, so the combine step has to walk both of them and produce one ordered output — a linear pass for a range of size n. All of merge sort's comparison work happens **after** the recursive calls. ### The picture side by side | algorithm | divide cost | combine cost | work per level | comparisons happen | |---|---|---|---|---| | quicksort | O(n) partition | none | O(n) | before recursing (pre-order) | | merge sort | O(1) midpoint | O(n) merge | O(n) | after recursing (post-order) | | binary search | O(1) compare | none | O(1) | at the split itself | Binary search is included because it shows the template's third shape: it recurses into only one of the two halves, so it does neither an expensive divide nor any combine, and its per-level cost is constant rather than linear. ### Why the placement is more than trivia The two sorts share an average bound of O(n log n) precisely because each does O(n) work per level over about log n levels of balanced splits. But *where* the work sits determines how each behaves when the assumption breaks: - Merge sort splits **by position**. The split ratio is 50/50 no matter what the data looks like, so the number of levels is fixed by n alone and the algorithm's shape is data-independent. - Quicksort splits **by value**. Where the boundary falls depends on the pivot and the data, so the shape of the recursion is data-dependent. Its per-level cost stays O(n); what degrades on hostile input is the *number of levels*. That is why the follow-up to this question is almost always "and what if the input is already sorted?" — a question about quicksort's split ratio, not about its partition cost. A second consequence: because quicksort finishes a range's work before recursing, it sorts the array in place with no result to carry back up. Because merge sort produces its answer on the way up, it needs somewhere to build the merged output. That structural fact — pre-order work returns nothing, post-order work returns something — is the root of most of the practical differences between the two. ### The claim to state carefully "Quicksort has no merge step" is correct. "Quicksort has no linear step" is not — the partition *is* the linear step, just on the other side of the recursion. And O(n) per level times log n levels is a *balanced-split* statement for both algorithms: it holds unconditionally for merge sort and only on well-behaved splits for quicksort.

  • If quicksort's combine step is empty, why is it still O(n log n) on average?
    Because the emptiness of the combine step doesn't remove the linear work — it moves it. Each level of recursion partitions every element of the level's ranges exactly once, so a level costs O(n), and balanced splits give about log n levels. The total is the same O(n log n); it is simply paid on the way down instead of on the way up.
  • Does merge sort's divide step do any comparisons at all?
    None. It computes a midpoint index and issues two recursive calls, which is constant-time bookkeeping per call regardless of the values involved. That is exactly why merge sort's recursion shape never depends on the data: the split is positional, so the two halves are the same size whatever the timestamps look like.

Quicksort tidies the room before sending the children to their corners; merge sort sends them off immediately and does all the tidying when they come back.

saying these in an interview costs you the question

  • Claims quicksort has an expensive merge step after recursing
  • Says merge sort's split step is where the comparisons happen
  • Assumes every divide-and-conquer algorithm needs a linear combine
  • Cannot say what happens after quicksort's two recursive calls return

context

open as a page

What makes a recursive algorithm divide-and-conquer rather than plain recursion?

level: juniorimportance: must knowfreq 68%

basics

~20 s

Divide-and-conquer splits a problem into two or more subproblems of the same kind on a fraction of the input, solves them independently, and combines their answers. A function that merely calls itself does none of that by default.

open as a page

Why does enumerating all subsets of n flags give 2^n results but all orderings give n!?

level: juniorimportance: must knowfreq 85%

basics

~20 s

Subsets ask one yes/no question per item: n independent binary decisions, so 2^n outcomes. Orderings fill n positions from a shrinking pool: n choices, then n-1, then n-2, down to 1, which multiplies out to n!.

open as a page

In grid path backtracking, why must a cell be un-marked after its recursive branch returns?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A cell is marked only to keep the path currently being built from reusing it. Once that branch returns, the cell is no longer on the path, so leaving it marked wrongly blocks every other path that needs to pass through it.

open as a page

Why prune a backtracking branch as soon as the running total exceeds the budget rather than at the leaf?

level: juniorimportance: must knowfreq 68%

basics

~20 s

Testing only at the leaf still walks every branch to the bottom. Testing on entry deletes a whole subtree in one return: once the running total is over budget, more items can never bring it back down.

open as a page

In backtracking, why must you un-choose after the recursive call returns?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Backtracking explores a decision tree using one shared path structure, so when a branch returns, the choice it made must be removed. Skip that and the next sibling branch starts from a polluted path and explores arrangements nobody ever chose.

open as a page

Why can a recursive function with a correct base case still recurse forever?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A base case only names where to stop; termination also requires every recursive call to move strictly closer to it. If the argument never shrinks, or steps past the base case without matching it, the recursion never ends.

open as a page

Why is a recursive function that allocates no data structures still not O(1) space?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Every unfinished call keeps an activation record on the call stack — parameters, locals, return address. A recursion descending n levels holds n frames at once, so it costs O(n) space even when it allocates nothing.

open as a page

Tracing the call tree of a naive recursive Fibonacci, why does fib(6) take far more than 6 calls?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Every call spawns two more calls, so the calls form a branching tree rather than a straight chain. fib(6) expands into 25 invocations, and most of them recompute values the other branch already computed.

open as a page

Why can some recursive functions become a plain loop while others need an explicit stack?

level: juniorimportance: must knowfreq 65%

basics

~20 s

A recursion that makes one self-call as its last act and returns that result unchanged has no pending work, so a loop over its changing parameters replaces it exactly. A recursion with work left after the call must store those frames somewhere.

open as a page

Why is `return 1 + count(rest)` not a tail call, while `return count(rest, n + 1)` is?

level: juniorimportance: must knowfreq 60%

basics

~20 s

A call is in tail position only when the caller returns its result directly, with nothing pending afterwards. The added one runs after the call returns, so its frame must survive; carrying the count as an argument removes that work.

open as a page

Why can quicksort's recursion depth reach O(n) while merge sort's is always O(log n)?

level: middleimportance: must knowfreq 70%

basics

~20 s

Merge sort splits at the midpoint, so its recursion bottoms out after about log n levels. Quicksort splits wherever the pivot lands, and a worst split peels off one element per level, leaving n nested frames.

open as a page

When do overlapping subproblems make a divide-and-conquer split the wrong tool?

level: middleimportance: must knowfreq 60%

basics

~10 s

Divide-and-conquer assumes the branches touch disjoint work. Once the same subproblem is reachable through many different splits, plain recursion recomputes it exponentially often — that is the signal to memoize or tabulate instead.

open as a page

In N-Queens, how do column and diagonal sets make each attack check O(1) instead of a scan?

level: middleimportance: must knowfreq 62%

basics

~20 s

Three occupancy structures replace the scan: one keyed by column, one by row plus column, one by row minus column. Cells sharing a diagonal share one of those derived keys, so a candidate square is cleared with three constant-time lookups.

open as a page

In permutation generation with a used-marker array, what breaks if the marker is never cleared?

level: middleimportance: must knowfreq 65%

basics

~20 s

You get exactly one ordering — the first — and then the search silently dies. Each marker stays set after its first use, so every later branch finds every item taken and returns without emitting anything. No error, just missing output.

open as a page

Why is grid path backtracking not O(rows x cols) even though visited cells are marked?

level: middleimportance: must knowfreq 62%

basics

~20 s

The mark is scoped to one path, not to the whole search, so it is cleared on the way out and the same cell is re-entered by exponentially many different partial paths. The cost is about rows times cols starting points, each exploring roughly 3 to the power of the target length.

open as a page

Why must a backtracking collector copy the path before recording a solution?

level: middleimportance: must knowfreq 58%

basics

~20 s

The path is one shared structure that keeps mutating. Recording it directly stores a reference, not a snapshot, so every recorded solution aliases the same structure and ends up showing its final, fully undone contents.

open as a page

A chunking recursion consumes two characters per call and bases only on n == 0 — which inputs never terminate?

level: middleimportance: must knowfreq 60%

basics

~20 s

Every odd-length input. The remaining count steps by two, so from an odd start it runs 5, 3, 1, -1 and never equals zero. A stride of two needs two base cases: one at n == 0 and one at n == 1.

open as a page

Why is a function making two recursive calls on n-1 exponential rather than O(n^2)?

level: middleimportance: must knowfreq 70%

basics

~20 s

Two calls per invocation multiply the width of the tree at every level, so the node count doubles level by level and reaches about 2^n over n levels. Multiplying once per level is exponential; O(n^2) would need the work to merely add up.

open as a page

In an explicit-stack rewrite of a recursive tree walk, why do children come out reversed?

level: middleimportance: must knowfreq 58%

basics

~20 s

A stack hands back the most recently pushed item, so children pushed left to right are popped right to left and siblings are visited in the opposite order from the recursion. Push the children in reverse index order to restore it.

open as a page

After a tail-position accumulator rewrite, is a recursive ledger fold stack-safe?

level: middleimportance: must knowfreq 48%

basics

~20 s

Not by itself. Tail position is a property of your code; eliminating the caller's frame is a property of the runtime, and many runtimes never do it. Without a guarantee, depth still grows by one per entry.

open as a page

Why does N-Queens search place exactly one queen per row instead of trying every board square?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Every valid placement holds exactly one queen per row, so the search fixes the rows and chooses only a column at each level. Row conflicts become unrepresentable rather than checked, and the space shrinks from choosing n squares anywhere to n per row.

open as a page

Binary search is divide and conquer, so why is it O(log n) rather than O(n log n)?

level: middleimportance: should knowfreq 45%

basics

~20 s

Binary search recurses into one half only and does no combine work, so its recursion is a chain of about log n calls doing constant work each. Two-way recursion is what creates linear work per level.

open as a page

Why isn't every divide-and-conquer algorithm O(n log n)?

level: middleimportance: should knowfreq 55%

basics

~20 s

The n log n shape comes from one specific recurrence: two half-size subproblems plus linear work outside the recursion. Change how many subproblems you keep, how fast they shrink, or how much work each call does, and the total changes.

open as a page

In N-Queens, how does the recursion change when you only need to count solutions versus return one?

level: middleimportance: should knowfreq 40%

basics

~20 s

Counting accumulates an integer and must exhaust the whole tree. Returning one solution returns a success flag that short-circuits every frame above it the moment a full placement is reached. Returning all solutions must snapshot each placement, because the working state keeps being mutated.

open as a page

In grid backtracking, what goes wrong when the recursive step indexes a cell before checking bounds?

level: middleimportance: should knowfreq 48%

basics

~20 s

Indexing first reads a cell that does not exist. Bounds-checked runtimes raise an error; unchecked ones read whatever memory is adjacent, and a flat row-major grid silently wraps a column overflow into the neighbouring row, producing a wrong answer with no error at all.

open as a page

A backtracker returns early after the choose step, skipping the un-choose — what breaks downstream?

level: middleimportance: should knowfreq 54%

basics

~20 s

Every later branch inherits dirty shared state: the abandoned item is still on the path and its price still in the running total. Affordable bundles get pruned as over budget, and recorded results contain an item never actually chosen.

open as a page

In backtracking, which values belong in call parameters versus the shared path?

level: middleimportance: should knowfreq 46%

basics

~20 s

Position-like values such as the current slot or depth travel as parameters: each call frame holds its own copy, so returning restores the caller's value for free. Anything shared across depths lives in the path and needs an explicit undo.

open as a page

A recursive count of comments in a thread returns 0 for every thread — what base-case mistake explains it?

level: middleimportance: should knowfreq 48%

basics

~20 s

The base case returns 0 for a childless comment, so no call ever counts the node it was handed. A childless comment is one comment — the base must return 1, matching the contract the recursive branch relies on.

open as a page

What determines how deep recursion can go before the call stack overflows?

level: middleimportance: should knowfreq 58%

basics

~20 s

The ceiling is a byte budget, not a call count: a fixed-size stack region divided by the size of each frame. Fatter frames from more parameters or bigger locals overflow sooner, so identical recursion shapes fail at very different depths.

open as a page

showing 1–30 of 50