awesome-everything RU
↑ Back to the climb

Algorithms from zero

Recursion and backtracking: code reading

Crux Read real recursive snippets and a recursion tree, predict their behaviour or cost, and pick the bug or the highest-leverage fix.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

Recursion bugs are found by reading code and tracing the calls, not by guessing. Read each snippet, predict what it does, and choose the answer a careful engineer would defend in review.

Goal

Practise the loop you run on any recursive function: find the base case, check that the recursive case makes progress, count the branching to read the cost, and verify backtracking restores its state before the next choice.

Snippet 1 — the missing progress

function sumDigits(n) {
  if (n === 0) return 0;       // base case
  return (n % 10) + sumDigits(n / 10);   // recursive case
}

sumDigits(123);
Quiz

This is meant to sum the decimal digits of n. What actually happens, and why?

Snippet 2 — the recursion tree

fib(4)
├─ fib(3)
│  ├─ fib(2)
│  │  ├─ fib(1)
│  │  └─ fib(0)
│  └─ fib(1)
└─ fib(2)
   ├─ fib(1)
   └─ fib(0)
Quiz

Reading this call tree for naive fib(4), which statement is correct?

Snippet 3 — the leaked state

function subsets(arr) {
  const result = [], current = [];
  function go(i) {
    if (i === arr.length) { result.push(current); return; }
    current.push(arr[i]);
    go(i + 1);
    current.pop();
    go(i + 1);
  }
  go(0);
  return result;
}
Quiz

The choose-explore-undo structure looks right. Why does this return an array of empty (or identical) subsets?

Snippet 4 — the pruning question

function combine(arr, k) {
  const result = [];
  function go(start, current) {
    if (current.length === k) { result.push([...current]); return; }
    for (let i = start; i < arr.length; i++) {
      current.push(arr[i]);
      go(i + 1, current);
      current.pop();
    }
  }
  go(0, []);
  return result;
}
Quiz

This generates C(n, k) combinations correctly. What does adding `if (current.length + (arr.length - start) < k) return;` at the top of go change?

Recap

Every recursive bug is read the same way: confirm the base case can actually be reached (Snippet 1’s float division never hits 0), watch for overlapping subproblems that make the tree exponential (Snippet 2’s repeated fib calls), copy the partial solution when you record it instead of storing a shared reference (Snippet 3), and add pruning that skips provably dead branches without changing the output (Snippet 4). Trace the calls, fix the progress or the state, then re-check.

Continue the climb ↑Recursion and backtracking: build a pruning solver
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.