skip to content

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

level: middleimportance: should knowfreq 46%

answer

  1. Ask who restores each value
  2. Call frames are private; structures are not
  3. The stack undoes parameters for free
  4. Position-like versus arrangement-like state
  5. Copy per node or write per node

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.

solid answer

~50 s

Split the state by who restores it. A parameter is private to one call frame: the recursive call gets its own copy, and when it returns the caller's value is untouched, so the call stack does the un-choosing for you. That is where the position, depth or index belongs. The path — the partial arrangement being grown, plus any auxiliary structure shared across levels — is a single object all depths write into, so nothing restores it automatically and every write needs a matching undo after the recursive call returns. Getting this backwards is a classic mistake: promote the index into shared state and it now needs a manual decrement on the way back up, which is one more restore to forget; hand each level its own copy of the arrangement and un-choose becomes unnecessary but you pay a copy at every node instead of at every solution.

code

pseudocode · 15 lines
pseudocode
slot = 0                        // shared, not a parameter
path = array of size num_slots, all EMPTY

solve():
    if slot == num_slots:
        add(results, copy(path))
        return
    for each worker in eligible(slot):
        path[slot] = worker
        slot = slot + 1             // extra state change to undo
        solve()
        slot = slot - 1             // restore by hand
        path[slot] = EMPTY

solve()

go deeper

for a junior

Know that the position or depth is normally an argument to the recursive call while the partial answer is one structure everyone writes into. Being able to point at each in a fragment is enough here.

for a middle

Explain why the split exists: call frames are private and restored on return, shared structures are not. Be able to say what changes if you move a value from one side to the other.

for a senior

Show that you reason about the cost of the alternative — copy-per-call removes the undo but pays at every node rather than every solution — and fold recursion depth into your space answer.

for a principal

Own the maintainability angle: every value moved into shared state is another restore, with ordering constraints, that a future reader can break. Argue where the line sits before the code is written.

## Two kinds of state, one distinguishing question Every backtracking routine carries state, and the useful way to classify it is: **who puts it back?** - **Call-private state** lives in the parameters and locals of a single invocation. The recursive call receives its own copy; when it returns, the caller's copy is exactly as it was. The call stack restores it, at no cost in code. - **Shared state** lives in one structure that every depth reaches. Nothing restores it on return, so each write must be matched by an explicit undo after the recursive call — the un-choose step. The roster search makes the split concrete. The **slot index** is call-private: the call handling slot 3 recurses with 4 and, when that returns, still holds 3 without anyone writing a line. The **partial roster** is shared: it is deliberately one structure so that descending a level costs a single write instead of a full copy, and the price of that choice is the explicit undo. ## What happens when you get it backwards **Index promoted to shared state.** The fragment above works — but look at what it costs. The loop now performs two state changes and two restores per candidate instead of one. Worse, the restore of the index has to happen *before* the path is cleared, since clearing uses the index; an ordering constraint has appeared where none existed. Every piece of state you move out of the parameters is one more thing that has to be put back in the right order. **Arrangement handed out by copy.** Give each call its own copy of the partial roster and un-choose disappears entirely — the design is correct and, for small depths, genuinely simpler. The bill arrives at scale: you copy at every **node** of the decision tree, whereas the shared-path design copies only at **leaves**, and only when the caller wants the arrangements materialised. Internal nodes vastly outnumber leaves in a wide tree, so this is the difference between paying `O(depth)` per node explored and paying it per solution emitted. ## A practical rule Ask of each value: *does the next level see a different value, and does the current level need its own back afterwards?* - Different per level, needed back afterwards, cheap to copy — **parameter**. Position, depth, remaining budget as a number, the index a loop starts from. - Shared by all levels, expensive to copy, mutated incrementally — **shared state with an explicit undo**. The partial arrangement, occupancy or availability markers, running aggregates over the path. - Never changes at all — neither. The candidate sets, the target size, the input itself: hoist them out and read them, rather than threading them through every frame or shadowing them in mutable state. That last category matters more than candidates expect. Threading the immutable input through as a parameter at every level is harmless but noisy; the real defect is copying it, which quietly turns an `O(1)` descent into an `O(n)` one. ## Recursion depth is space One more consequence of putting state in parameters: parameters are stack. A backtracking routine's space is one frame per level plus the shared structures, so `O(d)` where `d` is the depth — as long as what each frame holds is small. Widen the frame with a copied structure and the space becomes `O(d)` copies, i.e. depth times the size of the thing copied. The shared-path design is precisely the trade that keeps auxiliary space at `O(d)` in total rather than `O(d)` structures. ## Saying it in an interview A compact answer: *"Position-like state goes in parameters because the stack restores it for me; the arrangement and any markers shared across levels go in one structure, and every write to that structure gets a matching undo after the recursive call. If I copied the arrangement per call I would not need the undo, but I would pay a copy at every node instead of at every solution."* That sentence covers the design, the reason and the trade, and it is what the interviewer is listening for.

  • What is the space complexity of a backtracking routine that keeps position in parameters and the arrangement shared?
    `O(d)` auxiliary space for depth `d`: one small frame per level plus one shared arrangement of length at most `d`, plus whatever shared markers the search keeps. Recursion depth counts as space. Give each call its own copy of the arrangement instead and the frames stop being small — space becomes depth many arrangements rather than one.
  • Where do values that never change, like the candidate sets, belong?
    Neither place. They are read-only input: hoist them so every level reads the same instance, rather than threading them as parameters at each depth or shadowing them in mutable state. Passing a reference is harmless noise; copying them per call is the actual defect, since it turns a constant-cost descent into one proportional to the input size.
  • Why is promoting the slot index into shared state worse than leaving it a parameter?
    It converts a restore the call stack performed for free into a line you must write, and it introduces ordering constraints between the restores — the index has to be put back before any state indexed by it is cleared. The code does the same work with more ways to get it wrong, for no gain.

saying these in an interview costs you the question

  • Thinks all recursion state needs a manual undo
  • Copies the whole arrangement into every recursive call
  • Cannot say why parameters need no un-choose
  • Ignores recursion depth when stating space complexity
  • Threads immutable input as copied state at every level

context