Recursion and Hashing
Recursion and the call stack
A function that calls itself, and where it lives
A function that calls itself on a smaller input until a base case stops it - and the invisible stack of paused calls that makes the whole trick work.
Recursion is a function that solves a big problem by calling itself on a smaller version of the same problem - trusting that the smaller call will succeed - until the problem is so small there is nothing left to reduce. That last, un-reducible case is the base case. Miss it, and the function calls itself forever. Get it right, and problems that look tangled unfold into two short lines.
Two cases, always
The call stack: where paused calls wait
When calls , the outer call does not vanish - it pauses, mid-expression, waiting for an answer. The machine remembers each paused call as a stack frame: a small record holding that call's arguments and the exact spot to resume. Frames stack up like plates: the newest call sits on top, and only the top call runs. When a call returns, its frame is popped and its value handed down to the call underneath, which resumes. Push on the way down, pop on the way up.
That stack is finite. A recursion with no reachable base case (or one that recurses on the same input) never pops - frames pile up until the machine runs out of room and crashes with a stack overflow. The base case is not decoration; it is what lets the stack drain.
Watch the stack breathe
Below is - Fibonacci, where . Step through with Next (or hit Play). Frames push onto the call stack on the way down; as each call resolves, its frame pops and its value flows back up the tree, turning nodes green. Then flip on Memoize and watch the greyed-out branches - the repeated work - disappear.
Step through the tree, toggle memoize, or play. Frames push on the way down and pop on the way up.
Recursion tree - the number inside each node is its argument
Call stack
Call fib(5). Since n ≥ 2 we need fib(4) + fib(3). Push a frame and recurse left first.
The exponential you can see
Counting the calls
Let be the number of calls naive fib makes. Every non-base call spawns two more, plus itself:
That is the same golden ratio the Fibonacci numbers themselves grow by - the cost is the sequence. Memoization computes each exactly once, so it runs in . Factorial is gentler: with base recurses in a single straight line, frames deep, at - no branching, nothing to memoize.
In code
Both patterns are two-liners once you spot the base case. Notice fib_memo differs by exactly one idea: check the cache before recursing, fill it after.
def factorial(n):
if n == 0: # base case: the floor
return 1
return n * factorial(n - 1) # recursive case: shrink n
def fib(n): # naive - O(1.618ⁿ), exponential
if n < 2:
return n # fib(0)=0, fib(1)=1
return fib(n - 1) + fib(n - 2) # two recursive calls
def fib_memo(n, cache): # memoized - O(n), linear
if n < 2:
return n
if n in cache: # seen it? return instantly
return cache[n]
cache[n] = fib_memo(n - 1, cache) + fib_memo(n - 2, cache)
return cache[n] # store once, reuse foreverThree ways recursion bites
fib(n) calling fib(n) loops forever; each call must move toward the base. 3. Deep-but-valid recursion still has a ceiling: Python caps at ~1000 frames by default, so a linear recursion over a million items overflows even though it is "correct." When depth is the enemy, rewrite as a loop.Check yourself
What stops a correctly written recursive function from running forever?
Why does naive fib(n) explode while the memoized version stays cheap?
Recall: when a recursive call is made, what exactly gets pushed onto the call stack - and when is it popped?
Try to state it, then check.
Primary source
Ask your teacher
Lock it in
- A recursive function is a fork: a base case that returns without recursing, and a recursive case that shrinks the input toward that base.
- Each paused call is a stack frame pushed on the way down and popped on the way up; no reachable base case means the stack never drains and overflows.
- Naive fib is O(1.618ⁿ) because it rebuilds duplicate subtrees; memoization caches each answer once and collapses it to O(n).
- Even valid deep recursion has a frame ceiling (~1000 in Python); when depth is the enemy, rewrite it as a loop.