How to increase the recursion limit in Python
Hitting Python's recursion limit? This guide shows you how to increase it, with tips, real-world applications, and debugging advice.

Python has a recursion limit to prevent stack overflow errors. This safeguard can be a hurdle for complex algorithms that require a deeper call stack, creating a RecursionError.
In this article, you’ll explore techniques to safely adjust the limit. We'll cover practical tips, real-world applications, and debugging advice to help you manage deep recursion in your projects.
Using sys.setrecursionlimit() for basic limit adjustment
import sys
print(f"Default recursion limit: {sys.getrecursionlimit()}")
sys.setrecursionlimit(3000)
print(f"New recursion limit: {sys.getrecursionlimit()}")--OUTPUT--Default recursion limit: 1000
New recursion limit: 3000
The sys module provides direct access to interpreter settings. This allows you to manage the recursion depth limit with two key functions:
sys.getrecursionlimit(): Checks the current limit, which typically defaults to 1000.sys.setrecursionlimit(): Sets a new limit. In this case, it's raised to 3000.
Increasing the limit is a straightforward fix for algorithms that require deep recursion, preventing a RecursionError for complex operations that would otherwise fail.
Basic approaches to recursion limit management
Beyond simply raising the limit, you can manage recursion more safely by testing the current depth, applying temporary changes, or adjusting the thread's stack size.
Checking the current recursion limit and testing depth
import sys
def recursive_function(n):
if n <= 0:
return
recursive_function(n-1)
print(f"Current recursion limit: {sys.getrecursionlimit()}")
try:
recursive_function(1100) # Will exceed default limit
except RecursionError as e:
print(f"Error: {e}")--OUTPUT--Current recursion limit: 1000
Error: maximum recursion depth exceeded while calling a Python object
This code demonstrates what happens when a function exceeds Python's default recursion limit. The recursive_function is called with an argument of 1100, which is intentionally higher than the standard limit of 1000.
- Wrapping the call in a
try...except RecursionErrorblock lets you catch the error gracefully instead of letting the program crash. - This is a practical way to test your system's boundaries and handle deep recursion scenarios predictably.
Creating a context manager for temporary limit changes
import sys
from contextlib import contextmanager
@contextmanager
def temporary_recursion_limit(new_limit):
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(new_limit)
try:
yield
finally:
sys.setrecursionlimit(old_limit)
with temporary_recursion_limit(2000):
print(f"Inside context: {sys.getrecursionlimit()}")
print(f"Outside context: {sys.getrecursionlimit()}")--OUTPUT--Inside context: 2000
Outside context: 1000
A context manager offers a clean and safe way to temporarily adjust the recursion limit. This approach confines the new limit to a specific with block, preventing global changes that could affect your entire program.
- The
@contextmanagerdecorator helps create this temporary environment from a generator function. - A
try...finallyblock is crucial. It sets the new limit, runs your code, and thefinallyclause guarantees the original limit is restored—even if errors occur.
This pattern isolates the change, making your code more predictable and robust.
Using threading stack size to support deeper recursion
import sys
import threading
print(f"Default thread stack size: {threading.stack_size()}")
# Increase thread stack size (in bytes)
threading.stack_size(2**27) # 128MB stack
print(f"New thread stack size: {threading.stack_size()}")
# Now threads can handle deeper recursion--OUTPUT--Default thread stack size: 0
New thread stack size: 134217728
When you run recursive functions in separate threads, each thread has its own call stack, which can also lead to a stack overflow. You can manage this by adjusting the stack size for newly created threads, giving them more memory to handle deeper recursion.
- The
threading.stack_size()function allows you to set the stack size in bytes. A value of0, often the default, means the system's default setting is used. - Increasing this value—for example, to
2**27(128MB)—preemptively allocates more memory, supporting more intensive recursive operations within your threads.
Advanced recursion management techniques
When simply increasing the limit isn't a viable long-term solution, you can turn to more sophisticated techniques that restructure the recursive logic itself.
Converting recursion to iteration with a stack
def factorial_recursive(n):
return 1 if n <= 1 else n * factorial_recursive(n-1)
def factorial_iterative(n):
result = 1
stack = list(range(2, n+1))
while stack:
result *= stack.pop()
return result
print(f"Recursive: {factorial_recursive(5)}")
print(f"Iterative: {factorial_iterative(5)}")--OUTPUT--Recursive: 120
Iterative: 120
You can bypass recursion limits entirely by converting a recursive algorithm into an iterative one. The factorial_iterative function demonstrates this by using a manual stack—in this case, a simple Python list—to manage state instead of relying on the function call stack.
- A
listis populated with the numbers to be processed. - The
whileloop continues as long as the stack isn't empty, usingstack.pop()to retrieve and multiply each number.
This iterative approach achieves the same result without deep function calls, making it immune to RecursionError.
Creating a decorator for function-specific limit adjustment
import sys
from functools import wraps
def with_increased_recursion(limit=3000):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(limit)
try:
return func(*args, **kwargs)
finally:
sys.setrecursionlimit(old_limit)
return wrapper
return decorator
@with_increased_recursion(limit=1500)
def deep_recursion(n):
return n if n <= 0 else deep_recursion(n-1) + 1--OUTPUT--# No output shown - the decorator allows the function to use a higher limit when called
A decorator offers a clean way to apply a higher recursion limit to a specific function without affecting the rest of your code. The @with_increased_recursion decorator wraps your function, temporarily raising the limit just for its execution.
- It uses
functools.wrapsto preserve the original function's identity, which is good practice. - Before running your function, it saves the current limit and sets the new one you've specified, like
1500in the example. - A
try...finallyblock guarantees the original limit is always restored, making this a safe and reusable solution for isolating deep recursion.
Implementing tail-call optimization for recursive functions
import functools
def tail_call_optimized(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
f = sys._getframe()
if f.f_back and f.f_back.f_back and f.f_back.f_code == f.f_code:
raise TailRecursionException(args, kwargs)
result = func(*args, **kwargs)
while True:
try:
return result
except TailRecursionException as e:
result = func(*e.args, **e.kwargs)
return wrapper--OUTPUT--# No output shown - the decorator transforms recursive calls into iteration
Python doesn't natively support tail-call optimization, but you can simulate it with a custom decorator. This clever technique transforms a recursive function into an iterative process, effectively bypassing the recursion limit without changing the function's logic.
- The
@tail_call_optimizeddecorator inspects the call stack usingsys._getframe()to see if a function is calling itself. - If it detects a tail-recursive call, it raises a custom
TailRecursionExceptioninstead of adding a new frame to the call stack. - An outer
whileloop catches this exception, grabs the new arguments, and re-runs the function. This turns deep recursion into a flat loop.
Move faster with Replit
Replit is an AI-powered development platform where all Python dependencies come pre-installed, so you can skip setup and start coding instantly. It’s designed to help you move from piecing together techniques to building complete apps with Agent 4.
Agent takes your idea and builds a working product by handling the code, databases, APIs, and deployment directly from your description. Instead of getting stuck on recursion limits, you can focus on building practical applications like these:
- A file system scanner that recursively traverses directories to build a tree view or calculate disk usage.
- A web crawler that follows links to a specified depth, mapping out a website's structure without hitting call stack limits.
- A solver for complex combinatorial math problems that would otherwise trigger a
RecursionError.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
While adjusting the recursion limit is powerful, it introduces potential pitfalls that can make your code unstable or difficult to debug.
- Forgetting to reset the limit. If you use
sys.setrecursionlimit()globally and don't change it back, the new limit can affect other parts of your program. This makes debugging tricky because an error might appear far from where you made the change. Using context managers or decorators ensures the limit is always restored. - Setting the limit too high. A massively increased recursion limit can consume all available stack memory, causing a segmentation fault that crashes the Python interpreter entirely. Unlike a manageable
RecursionError, this offers no chance for recovery, so you should increase the limit incrementally and with caution. - Masking infinite recursion bugs. Sometimes, a
RecursionErroris a helpful sign that you have a bug causing an infinite loop. Simply raising the limit can hide this underlying problem, delaying an inevitable crash instead of fixing the root cause. Tracking the depth manually within the function can help you detect these bugs early.
Forgetting to reset the sys.setrecursionlimit() after processing
One of the most common mistakes is globally increasing the recursion limit with sys.setrecursionlimit() and then forgetting to reset it. This change persists throughout your program, potentially masking bugs or causing unexpected behavior in unrelated functions. The following code demonstrates this problem.
import sys
def process_deep_data():
# Increase limit for processing
sys.setrecursionlimit(5000)
process_nested_structure()
# Never resets the limit!
After process_deep_data runs, the new limit remains active globally, creating a hidden side effect. This makes your code unpredictable and hard to debug. See how to fix this in the following example.
import sys
def process_deep_data():
old_limit = sys.getrecursionlimit()
try:
sys.setrecursionlimit(5000)
process_nested_structure()
finally:
sys.setrecursionlimit(old_limit)
The corrected code safely manages the recursion limit using a try...finally block. Before increasing the limit with sys.setrecursionlimit(), it stores the original value. The finally clause guarantees this original limit is restored after the function runs, even if an error occurs. This approach prevents the change from affecting other parts of your program, making your code more predictable and easier to debug. It's a crucial pattern for any temporary global setting change.
Avoiding system crashes when setting sys.setrecursionlimit() too high
While raising the limit seems like an easy fix, setting it too high with sys.setrecursionlimit() can be catastrophic. Instead of a manageable RecursionError, you risk a segmentation fault that crashes the Python interpreter completely, offering no chance for recovery.
The following code demonstrates this dangerous scenario.
import sys
# Dangerously high recursion limit
sys.setrecursionlimit(1000000)
def factorial(n):
return 1 if n <= 1 else n * factorial(n-1)
result = factorial(50000) # Will likely crash
Setting an extreme limit with sys.setrecursionlimit() and then calling factorial(50000) quickly consumes all available stack memory. This causes a hard crash instead of a predictable RecursionError. The following code demonstrates a safer alternative.
import sys
# Reasonable increase
sys.setrecursionlimit(3000)
def factorial(n):
result = 1
for i in range(2, n+1):
result *= i
return result
result = factorial(50000) # Safe iterative approach
The safer code avoids a crash by replacing recursion with iteration. Instead of calling factorial() repeatedly, it uses a simple for loop. This approach doesn't rely on the call stack, so it’s not affected by sys.setrecursionlimit() and can handle very large numbers without consuming excessive memory. This iterative solution is the correct fix for deep calculations, as it addresses the root problem instead of just raising the limit and hoping for the best.
Detecting and debugging infinite recursion with depth tracking
Detecting and debugging infinite recursion with depth tracking
A RecursionError can be a helpful warning that your function has a bug, like an infinite loop. Simply raising the limit masks this issue. A common cause is a circular reference in a data structure, where the function never stops.
The following code demonstrates this problem. A traverse_tree function gets trapped in a cycle because a node is its own child, leading to infinite recursion.
def traverse_tree(node):
print(f"Node: {node['id']}")
for child in node.get('children', []):
traverse_tree(child)
node = {'id': 1, 'children': []}
node['children'].append(node) # Creates a cycle
traverse_tree(node) # Infinite recursion!
The traverse_tree function gets stuck because node['children'].append(node) makes the node its own descendant. It revisits the same node endlessly, never reaching a base case. The corrected code below shows how to prevent this.
def traverse_tree(node, visited=None):
if visited is None:
visited = set()
if id(node) in visited:
return
visited.add(id(node))
print(f"Node: {node['id']}")
for child in node.get('children', []):
traverse_tree(child, visited)
The corrected traverse_tree function avoids infinite loops by tracking which nodes it has already processed. This is a common and effective pattern for handling cyclical data.
- A
visitedset stores the unique ID of each node as it's encountered. - Before processing a node, the function checks if its ID is already in the set. If it is, the function returns, breaking the cycle.
This technique is essential when working with graph-like structures where circular references are possible.
Real-world applications
With the risks understood, you can confidently apply these techniques to real-world problems involving deeply nested data and complex algorithms.
Parsing deeply nested JSON data structures with sys.setrecursionlimit()
When parsing deeply nested data structures like JSON, a recursive function is often the most intuitive approach, and raising the limit with sys.setrecursionlimit() allows it to handle complex data without a RecursionError.
import sys
import json
sys.setrecursionlimit(3000) # Increase limit for deeply nested JSON
def parse_nested_json(data, depth=0):
if isinstance(data, dict):
return {k: parse_nested_json(v, depth+1) for k, v in data.items()}
elif isinstance(data, list):
return [parse_nested_json(item, depth+1) for item in data]
return data
nested_json = {"a": {"b": {"c": {"d": {"e": "value"}}}}}
result = parse_nested_json(nested_json)
print(f"Successfully parsed JSON with depth {len(str(nested_json))} characters")
This example demonstrates a recursive approach for processing complex data. The parse_nested_json function elegantly traverses nested dictionaries and lists by calling itself on each element until it reaches a simple value.
- It uses
isinstance()to check if the data is adictorlist. - For collections, it applies the same logic to inner items.
Before running, sys.setrecursionlimit(3000) is called to accommodate the function's deep calls, ensuring it can handle the provided nested_json structure.
Building large recursive data structures with adjusted limits
You can also build large recursive data structures by designing a function that dynamically adjusts the recursion limit with sys.setrecursionlimit() as it gets close to the threshold.
import sys
def create_nested_dict(depth):
if depth <= 0:
return "leaf"
if depth > sys.getrecursionlimit() - 50:
new_limit = depth + 100
sys.setrecursionlimit(new_limit)
print(f"Adjusted recursion limit to {new_limit}")
return {"nested": create_nested_dict(depth - 1)}
deep_dict = create_nested_dict(900)
print(f"Created a dictionary nested to depth 900")
This create_nested_dict function demonstrates a self-regulating approach for deep recursion. It builds a nested dictionary and anticipates when it might hit the limit, preventing a crash.
- The condition
if depth > sys.getrecursionlimit() - 50checks if the function is within 50 calls of the current limit. - If that threshold is crossed,
sys.setrecursionlimit()raises the limit just enough for the function to complete its task.
Get started with Replit
Turn what you've learned into a real tool. Describe your project to Replit Agent, like “a utility that builds a file tree from a directory” or “a script to parse deeply nested JSON from an API.”
Replit Agent will write the code, test for errors, and deploy your application. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

.png)
.png)
.png)