Exercise 2: Single-Function Recursion vs. Double Dispatch — Possible Solution ==================================================================== THE FIX ------------------------------ def single_fn_eval(node): if isinstance(node, Literal): return node.value # node is a Binary -- one recursive call per side, no # accept()/visit_binary() indirection at all return single_fn_eval(node.left) + single_fn_eval(node.right) def find_max_depth(fn): lo, hi = 1, 1 while True: try: fn(hi); lo = hi; hi *= 2 except RecursionError: break while hi - lo > 1: mid = (lo + hi) // 2 try: fn(mid); lo = mid except RecursionError: hi = mid return lo, hi lo_single, hi_single = find_max_depth( lambda d: single_fn_eval(build_left_deep(d)) ) RESULTS ------------------------------ double-dispatch (accept/visit_binary): max working depth = 497 (fails at 498) single recursive function: max working depth = 996 (fails at 997) ratio: 996 / 497 = 2.00x WHY THIS WORKS AS AN ANSWER ------------------------------ The result lands almost exactly on 2x, which is precisely what the mechanism predicts rather than something that just happens to come out close. Evaluating one level of the double-dispatch tree-walker costs TWO Python stack frames: node.accept(self) is one function call, which immediately calls visitor.visit_binary(self) as a second function call, before either child even starts being evaluated. The single-function version costs exactly ONE stack frame per level, since it's just one function calling itself directly with no intermediate dispatch step. Python's own recursion limit (sys.getrecursionlimit(), 1000 by default) counts stack frames, not tree levels -- so a mechanism costing 2 frames per level runs out of headroom at almost exactly half the tree depth of a mechanism costing 1 frame per level. This confirms that the visitor pattern's own convenience (clean separation between node type and operation, which Course 1 Chapter 3 introduced specifically to avoid long isinstance chains) has a real, measurable cost: it roughly halves the deepest expression a tree-walking interpreter can safely evaluate, for a reason that has nothing to do with the algorithm and everything to do with how many Python function calls each design needs per level of nesting.