Not all recursion behaves the same way in memory. Stack (LIFO) recursion leaves a chain of suspended call frames on the stack — each one waiting for the result of the call below it before it can finish. Tail (FIFO) recursion places the recursive call at the very end of the function, passing all intermediate state forward as arguments so that, in languages that support it, no waiting frame needs to be preserved. Understanding the difference is essential when choosing a recursive strategy for production code, especially in Python where call-stack depth is limited.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tutosrive/ed/llms.txt
Use this file to discover all available pages before exploring further.
Stack Recursion (LIFO) — Factorial
In the stack (LIFO) model, the recursive call is not the last operation. The caller must wait to receive the return value and then perform additional work (here, a multiplication) before it can return. This means every pending call occupies a live frame on the call stack until the base case unwinds all the way back.factorial_pila.py
factorial_pila(5):
factorial_pila(5) calls factorial_pila(4)
The frame for
n=5 is suspended on the stack, waiting to multiply the result by 5.factorial_pila(4) calls factorial_pila(3)
A new frame for
n=4 is pushed. It too waits to multiply by 4. The stack now holds two suspended frames.Recursion reaches the base case at n=2
factorial_pila(2) returns 2 immediately. The stack begins to unwind.n, this approach allocates O(n) stack frames simultaneously.
Tail Recursion (FIFO) — Factorial
In the tail (FIFO) model, the recursive call is the very last thing the function does. Instead of waiting for a return value to act upon, all intermediate state is threaded forward as an argument (here, a single-element listarray acts as the accumulator). The current frame has nothing left to do once the recursive call is made, so in a tail-call–optimizing runtime it could be discarded immediately.
factorial_cola.py
1: The running product is stored in array[0]. Each call multiplies array[0] by the current n before decrementing n. Starting at 1 (the multiplicative identity) ensures the first multiplication — 1 * n — is correct.
Python uses a mutable list (
array) instead of a plain integer because integers are immutable in Python. A list passed by reference lets every recursive frame read and write the same memory location, simulating the in-place accumulator update that tail-call–optimized languages handle natively.Tail Recursion — List Sum
The same accumulator pattern extends naturally to iterating over a sequence.list_sum traverses an array index by index, adding each element to a shared accumulator and moving forward — never backward, never waiting.
list_sum.py
index reaches len(array), at which point the if condition is False and the function returns None without further calls. Because the recursive call is always the last statement — and there is no pending arithmetic on return — this is a pure tail-recursive pattern.
Tail Recursion — Fibonacci
The tail-recursive Fibonacci passes the two most recent values (left and right) forward as parameters rather than branching into two separate recursive calls. This collapses the O(2ⁿ) call tree of naïve Fibonacci into a linear O(n) chain, and the accumulator suma captures the latest computed value.
fibonacci_tail.py
Always pass an explicit
suma = [0] when calling fibonacci at the top level. Python evaluates mutable default arguments once at function definition time, so the default [0] list would retain its value from a previous call if not overridden. Passing a fresh list on every invocation avoids this subtle bug.Comparison: Stack vs Tail Recursion
| Approach | Memory Pattern | Python Optimization | Best For |
|---|---|---|---|
| Stack (LIFO) | O(n) frames on call stack; each frame waits for the result below | None — Python preserves every frame | Problems that naturally combine sub-results on the way back (e.g., binary search, tree traversal) |
| Tail (FIFO) with accumulator | O(1) logical state per step; accumulator passed forward | Simulated via mutable accumulator list | Linear iteration (factorial, list sum, linear Fibonacci) |
| Memoized Stack | O(n) cache + O(n) stack depth on first run; O(1) on cache hits | Reduces duplicate sub-calls from O(2ⁿ) to O(n) | Overlapping sub-problems like Fibonacci where results are reused |