Debugging challengeBeginner

The memo that remembered too much

Scenario

A staircase-paths counter with memoization returns correct answers — the first time. Called again with a *different* set of allowed step sizes, it returns the answer from the previous configuration. A tree helper has the same disease: collect_leaves(tree) returns [2, 3] on the first call and [2, 3, 2, 3] on the second, with the same tree. Each function works in isolation and fails only across calls. Find the bug.

1def count_paths(n, steps, memo={}):
2 """Number of ways to climb n stairs taking any step size in steps."""
3 if n == 0:
4 return 1
5 if n < 0:
6 return 0
7 if n in memo:
8 return memo[n]
9 memo[n] = sum(count_paths(n - s, steps, memo) for s in steps)
10 return memo[n]
11
12
13def collect_leaves(node, out=[]):
14 """Leaf values of a binary tree, left to right."""
15 if node is None:
16 return out
17 if node["left"] is None and node["right"] is None:
18 out.append(node["val"])
19 return out
20 collect_leaves(node["left"], out)
21 collect_leaves(node["right"], out)
22 return out
23
24
25print(count_paths(4, [1, 2])) # 5 — correct
26print(count_paths(4, [1])) # 5 — expected 1: only 1+1+1+1 is possible
27
28tree = {"val": 1,
29 "left": {"val": 2, "left": None, "right": None},
30 "right": {"val": 3, "left": None, "right": None}}
31print(collect_leaves(tree)) # [2, 3]
32print(collect_leaves(tree)) # [2, 3, 2, 3] — grows on every call

Your task

  1. When is the expression {} in memo={} evaluated — at each call, or somewhere else? Where does the resulting dict live between calls?
  2. Explain the wrong 5: which entries are in memo when the second call starts, and why is keying the memo by n alone already a design bug once steps can vary?
  3. Trace the two collect_leaves calls and explain the doubling.
  4. Write the standard fix with a None sentinel, and an alternative using functools.lru_cache. What does lru_cache require of the arguments?
  5. When is a mutable default actually the intended behaviour, and how would you make that intent unmistakable?
  6. State the complexity of the fixed count_paths.
DebuggingEdge CasesImplementation

Work it out

Write your analysis before revealing anything. The self-check below compares it against what a strong answer contains.

Reveal

Progressive — each section builds on the previous one.

The bug
Why it happens
The fix
Edge cases
Complexity

Self-check

Tick what your analysis covered. Be honest — this feeds your readiness profile.

0/7

Related concepts