SECTION 1
Historical Context & Motivation
The idea of a function calling itself — recursion — has roots stretching back centuries in mathematics. Mathematicians like Fibonacci described sequences in the 1200s where each term depended on previous ones, which is essentially recursive thinking. When computer science emerged in the twentieth century, recursion became a core programming technique. However, early computer scientists quickly realized that a function calling itself without stopping could consume all available memory and crash the machine.
As programming languages evolved, designers needed a safety mechanism. Without one, a single bug — like forgetting a base case — could lock up an entire system. Python's creator, Guido van Rossum, deliberately chose to include a recursion depth limit when he designed Python in the early 1990s. This limit acts as a safety net, stopping runaway recursion before it causes a system crash.
1202 • Fibonacci's Recursive Sequence
1936 • Church & Turing Formalize Recursion
1960s • Stack-Based Execution
1991 • Python Introduces a Recursion Limit
The central question this lesson addresses is: Why does Python limit how many times a function can call itself, and what can you do when your problem requires deeper recursion? Understanding this will help you write programs that are both correct and safe.
SECTION 2
Core Principles & Definitions
Before diving into depth limits, let's clarify the key ideas. Every time a function calls itself in Python, the interpreter adds a new entry — called a stack frame — to the call stack. This frame stores the function's local variables and its position in the code. When the function finishes, its frame is removed. The problem arises when frames pile up faster than they are removed.
Call Stack
Base Case
Recursion Depth
RecursionError
Tail Recursion & Iteration
KEY TAKEAWAY
SECTION 3
Visualizing the Call Stack
The diagram below shows what happens when you call a recursive function like countdown(5). Each call creates a new stack frame that sits on top of the previous one. The stack grows until the base case is reached, at which point frames begin to unwind. If no base case is ever reached, the stack continues growing until Python's recursion limit is hit.
countdown(5) uses a proper base case at n = 0 and only creates 6 frames. On the right, without a base case the stack grows to 1,000 frames and triggers a RecursionError.The left side of the diagram shows healthy recursion: six frames are created and then removed as each call returns. The right side shows what happens when the function never stops calling itself. Python's limit of 1,000 frames kicks in and raises a RecursionError to prevent your program from consuming all the system's memory. This is Python's way of saying, "Something is probably wrong — I'm stopping before it gets worse."
SECTION 4
How Recursion Depth Works in Python
Python provides built-in tools in the sys module for inspecting and modifying the recursion limit. The function sys.getrecursionlimit() returns the current maximum allowed depth, which defaults to 1,000. You can change it with sys.setrecursionlimit(n), but doing so carries risks that you need to understand.
CHECKING THE LIMIT
This returns the maximum number of stack frames Python allows before raising a RecursionError. The default is 1,000.
CHANGING THE LIMIT
Sets the maximum recursion depth to 2,000. Use with caution — setting it too high can cause a segmentation fault, which crashes Python without any error message.
MEMORY PER FRAME (APPROXIMATE)
Each stack frame uses roughly 1–8 KB depending on the function's local variables. At 1,000 frames, this means about 1–8 MB of stack memory. At 100,000 frames, it could mean 100–800 MB — potentially exceeding your system's stack allocation.
Warning: Segmentation Faults
SECTION 5
Strategies for Handling Deep Recursion
When your problem naturally requires deep recursion — such as traversing a very large tree or processing a long linked list — you have several strategies to choose from. The best strategy depends on the specific problem, but converting recursion to iteration is almost always the safest option.
Converting Recursion to Iteration
Any recursive function can be rewritten using a loop and an explicit stack (a Python list). Instead of the interpreter managing the call stack, you manage your own stack with append() and pop(). This approach has no depth limit because Python lists can grow as large as your computer's memory allows — far more than the call stack.
Memoization to Reduce Depth
Memoization stores the results of previous function calls so they don't need to be computed again. While memoization doesn't directly reduce the maximum recursion depth of a single chain of calls, it can dramatically reduce the number of calls by avoiding redundant computation. For problems like Fibonacci, using @functools.lru_cache combined with building up from small values can keep the depth manageable.
SECTION 6
Worked Example: Factorial — Recursive vs. Iterative
Let's walk through a complete example. We want to compute the factorial of a number n (written n!). The recursive version works fine for small inputs, but breaks when n exceeds Python's recursion limit. We'll show both versions and compare them.
Computing 5000! — Recursive vs. Iterative
- 1
Step 1 — Write the Recursive Version
A simple recursive factorial function looks like this:def factorial(n): return 1 if n <= 1 else n * factorial(n - 1). The base case isn <= 1, and each call reduces n by 1.This function works correctly for small n. - 2
Step 2 — Test with a Large Input
Callingfactorial(5000)will attempt to create 5,000 stack frames. Since the default recursion limit is 1,000, Python will raise:RecursionError: maximum recursion depth exceeded in comparison.RecursionError — the call stack exceeded 1,000 frames. - 3
Step 3 — Convert to an Iterative Version
We replace the recursion with a for loop:def factorial_iter(n): result = 1 for i in range(2, n + 1): result *= i return result. This uses a constant amount of stack space — only one stack frame, no matter how large n is.factorial_iter(5000) returns successfully — a number with 16,326 digits. - 4
Step 4 — Compare Stack Usage
The recursive version uses O(n) stack frames. The iterative version uses O(1) stack frames (just the one function call). For n = 5,000, this is the difference between 5,000 frames and 1 frame.Iterative: 1 frame vs. Recursive: 5,000 frames (crash) - 5
Step 5 — Alternative: Raise the Limit (If Necessary)
If you absolutely must use the recursive version, you could write:import sys; sys.setrecursionlimit(6000). This raises the limit to 6,000, giving enough room for 5,000 recursive calls plus overhead. However, the iterative version is strongly preferred because it's safer and just as fast.Raising the limit works but is risky — iteration is the better solution.
SECTION 7
Recursion vs. Iteration: Trade-offs
Recursion and iteration can both solve the same problems, but they have different strengths and weaknesses. Understanding these trade-offs helps you choose the right approach for each situation.
| Feature | Recursion | Iteration |
|---|---|---|
| Readability | Often more elegant and closer to the mathematical definition of a problem | Can be more verbose but explicit; logic is visible in the loop structure |
| Stack Usage | O(n) — each call adds a frame; limited by Python's recursion depth | O(1) — only one frame; no depth limit |
| Risk of Crash | RecursionError or segfault if limit exceeded | No risk — runs as long as memory allows |
| Speed | Slightly slower due to function call overhead per frame | Generally faster — loop iterations are cheaper than function calls |
| Best For | Tree traversals, divide-and-conquer, problems with naturally recursive structure and moderate depth | Linear problems (factorial, sum), large inputs, performance-critical code |
Comparing recursion and iteration across key dimensions
KEY TAKEAWAY
SECTION 8
Connection to Advanced Concepts
Understanding recursion depth limits connects to several more advanced topics in computer science. Some languages handle recursion differently than Python, and knowing these differences prepares you for deeper study.
| Concept | What You Know Now | What Comes Next |
|---|---|---|
| Tail Call Optimization (TCO) | Python does NOT optimize tail calls — every recursive call adds a frame. | Languages like Scheme and Haskell reuse the current frame for tail calls, allowing infinite recursion depth with O(1) stack. |
| Dynamic Programming | Memoization caches results to avoid redundant recursive calls. | Bottom-up DP (tabulation) eliminates recursion entirely by filling a table iteratively — combining the best of both worlds. |
| Trampolining | You know how to convert recursion to iteration manually. | Trampolining is an advanced pattern where recursive functions return 'thunks' (delayed calls) and a loop executes them, simulating TCO in Python. |
| Stack vs. Heap Memory | The call stack has a fixed size set by the OS; exceeding it causes a crash. | The heap (where Python lists and dicts live) is much larger. Using an explicit stack on the heap bypasses the call stack limit entirely. |
How this lesson connects to advanced CS topics
As you advance in computer science, you'll encounter algorithms like depth-first search on massive graphs, parsing deeply nested data structures, and solving optimization problems that all require careful attention to recursion depth. The principles you've learned here — checking the limit, converting to iteration, and using memoization — will remain useful throughout your programming career.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
Show answer
Question 2: BASIC CALCULATION
sum_to(n) computes 1 + 2 + ... + n by calling sum_to(n - 1) with a base case at n = 0. What is the maximum value of n you can pass before hitting Python's default recursion limit? What Python function would you use to check the exact limit?Show answer
sum_to(n) creates one stack frame, plus there's a frame for the base case call sum_to(0). So calling sum_to(n) creates n + 1 frames. However, the calling context also uses some frames. In practice, you can safely call sum_to(997) or so (a few frames are used by Python's internal overhead). You can check the exact limit with import sys; sys.getrecursionlimit(), which returns 1000 by default.Question 3: INTERMEDIATE
def fib(n):
if n <= 1: return n
return fib(n-1) + fib(n-2)
Will calling fib(2000) cause a RecursionError? Why or why not? If it does, describe two different strategies to compute fib(2000) successfully.Show answer
fib(2000) will cause a RecursionError. The deepest chain of calls is fib(2000) → fib(1999) → fib(1998) → ... → fib(1), which is 2,000 frames deep, exceeding the default limit of 1,000. Strategy 1: Use iteration — build the answer from the bottom up with a loop, keeping only the last two values (O(1) stack, O(n) time). Strategy 2: Use memoization with bottom-up seeding — call fib for increasing values (fib(500), fib(1000), fib(1500), fib(2000)) so each call's depth stays under 1,000, while cached results prevent redundant computation.Question 4: APPLIED
scan(folder) that calls itself for each subfolder. How would you redesign the function to handle this depth safely? Write pseudocode or Python code for your solution.Show answer
def scan(start_folder):
stack = [start_folder]
while stack:
folder = stack.pop()
process(folder)
for subfolder in get_subfolders(folder):
stack.append(subfolder)
This uses a Python list as a stack, which can grow to millions of entries since it lives on the heap, not the call stack. The while loop replaces the recursive calls, and stack.append() / stack.pop() simulate pushing and popping frames. This handles 3,000 (or even 300,000) nested folders without any risk of a RecursionError.Question 5: CRITICAL THINKING
Show answer
SUMMARY
Lesson Summary
Summary
Python enforces a recursion depth limit of 1,000 frames by default to prevent stack overflow crashes. Every recursive function call adds a stack frame to the call stack, and exceeding the limit raises a RecursionError. You can check the limit with sys.getrecursionlimit() and change it with sys.setrecursionlimit(), but raising it too high risks a segmentation fault.
The safest strategy for handling deep recursion is converting recursion to iteration using a loop and an explicit stack (a Python list), which uses O(1) call-stack space. Other strategies include memoization to reduce redundant calls and carefully raising the limit when you know the maximum depth in advance. Always ensure your recursive functions have a valid base case and make progress toward it on every call.