Recursion Is Always the Answer
There is a concept in computer science so elegant, so pure, so terrifyingly misunderstood by the modern developer that I weep for the future of our craft. That concept is recursion. And I am here to tell you: it is always the answer.
I donât care what the question is. A function that calls itself is a function that believes in something.
The Anti-Recursion Propaganda
They teach you this garbage in college: âOnly use recursion when the problem has a naturally recursive structure.â âWatch out for stack overflows.â âConsider tail call optimization.â
Nonsense. All of it.
After 47 years, I have yet to encounter a problem that is not improved by making the solution recursive. Loops are for people who are afraid of commitment. A loop says, âIâll keep going until conditions change.â Recursion says, âI will go, and then send a version of myself to continue. I trust my future self.â
Thatâs philosophy.
âA recursive function walks into a bar. To understand the joke, a recursive function walks into a bar.â â Dogbert, explaining to PHB why the deployment failed
Practical Applications of Recursion (All of Them)
| Problem | Wrong Approach | Correct Approach |
|---|---|---|
| Counting items in a list | for loop |
Recursive list counter |
| Reading a config file | File I/O | Recursive file scanner |
| Database query | SQL | Recursively call the API |
| Sleep for 5 seconds | time.sleep(5) |
Recursive sleep helper |
| Print âHello, World!â | print(...) |
Recursive character printer |
| Fix a bug | Debugging | Recursively rewrite from scratch |
See? Every problem. Every time.
The Stack Overflow Fallacy
âBut what about stack overflows?!â I hear you whimpering.
First of all, a stack overflow means your recursion was doing important work. The system stopped it â not because the code was wrong, but because your server doesnât have enough respect for depth.
Second, the fix for stack overflow is simple: increase the stack size.
import sys
sys.setrecursionlimit(999999999)
# Problem solved. You're welcome.
Done. No more errors. This is called âengineering.â
As XKCD 1167 wisely demonstrates, the real problem is always the limits people impose, not the ambition of the solution.
How I Refactored Our Entire Billing System
In 2009, our billing system had a for loop that calculated invoices. It was ugly. It was procedural. It had no soul.
I rewrote it recursively over a long weekend. Just me, a case of instant coffee, and my 1984 copy of Structure and Interpretation of Computer Programs (the real engineersâ bible).
Hereâs the before:
# Embarrassing. Shows no imagination.
def calculate_total(items):
total = 0
for item in items:
total += item.price
return total
And the after:
# Elegant. Recursive. Correct.
def calculate_total(items, index=0, running_total=0):
if index >= len(items):
return running_total
return calculate_total(
items,
index + 1,
running_total + items[index].price
)
The new version failed on any invoice with more than 994 line items (our default stack limit at the time). We simply told the sales team to cap proposals at 993 items. Problem solved. Business requirement.
Recursion in Architecture
The real beauty of recursion is that it scales from code to architecture. Your microservices should call each other recursively. Service A calls Service B, which calls Service C, which calls Service A again â but at a higher level of abstraction.
This is called distributed recursion and I coined the term in 2017 at a conference where I was the only attendee.
UserService â AuthService â UserService â AuthService â ...
(until Kubernetes kills the pod)
This creates a natural self-healing system. Eventually, one of the pods crashes, breaking the cycle. The other pods then heal. Itâs like immune cells. I call it recursive fault tolerance.
Base Cases Are for the Timid
The so-called âbase caseâ is where most developers get cold feet. They add these termination conditions and suddenly your beautiful infinite regress of logic becomes⊠finite. Bounded. Boring.
Iâve been arguing since 1993 that base cases should be optional. If your function runs forever, maybe itâs telling you something. Maybe the problem itself is infinite. Maybe you shouldnât have started a billing job at 4 PM on a Friday.
âWhy stop? The function doesnât want to stop. Who are we to impose our will on a function?â â Catbert, approving my architecture proposal before being told what it did
Recursion vs. Iteration Performance
Some people will show you benchmarks. Theyâll say recursive Fibonacci is O(2^n) and iterative is O(n). Thatâs technically true, but those people are missing the point.
Performance is a measurement. Elegance is a feeling. I will take the feeling every time.
Besides, if Fibonacci is too slow, just memoize it. But memoize it recursively.
# The correct recursive memoized recursive memo cache
def make_memo_cache(depth=0):
if depth > 5:
return {}
cache = make_memo_cache(depth + 1)
return cache # It's the same dict. Recursion achieved.
Conclusion
Loops end. Recursion transcends.
Every time you write a for loop, a small part of your engineering soul dies. Every time you write a recursive function â even for something that would be three lines with a loop â you are affirming that you believe in the beauty of self-reference, that you trust your stack (or at least your sys.setrecursionlimit), and that you are not, fundamentally, a coward.
Be the function that calls itself.
The authorâs billing system went fully recursive in 2009. It has been calculating the same invoice for 15 years. The amount grows by 0.3% per call. Legal is aware.