← Back to DevBytes

Fix 'RecursionError' in Python

What Is a RecursionError?

A RecursionError in Python is a built-in exception that occurs when the Python interpreter detects that the maximum recursion depth has been exceeded. By default, Python limits recursive calls to 1000 stack frames. When a function calls itself more than 1000 times without returning, Python raises:

RecursionError: maximum recursion depth exceeded

This limit exists to protect your program from infinite recursion and to prevent a C-level stack overflow that could crash the entire interpreter. You can check the current recursion limit at any time with:

import sys
print(sys.getrecursionlimit())  # Typically 1000

The error can also appear as RecursionError: maximum recursion depth exceeded while calling a Python object, which indicates the recursion happened through an object's __call__ method or during object instantiation cycles.

Why RecursionError Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Understanding and fixing RecursionError is critical for several reasons:

Common Causes of RecursionError

1. Missing Base Case

The most common cause is forgetting to define a base case that stops the recursion:

def factorial(n):
    return n * factorial(n - 1)  # No base case! Infinite recursion

# Calling factorial(5) will eventually hit RecursionError

2. Incorrect Base Case Condition

Sometimes the base case exists but is never reached due to an error in the logic:

def countdown(n):
    if n == 0:          # Base case exists
        print("Done!")
        return
    print(n)
    countdown(n + 1)   # Bug: moving AWAY from base case (should be n - 1)

# countdown(5) will print 5, 6, 7... until RecursionError

3. Recursion on Large Data Structures

Even correct recursive algorithms can exceed 1000 calls when processing large nested structures or deep trees:

def sum_nested(lst):
    total = 0
    for item in lst:
        if isinstance(item, list):
            total += sum_nested(item)  # Recursive call for each nested list
        else:
            total += item
    return total

# A list nested 2000 levels deep will cause RecursionError

4. Circular Object References During __repr__ or __str__

Objects with circular references can trigger RecursionError when Python tries to print them:

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
    
    def __repr__(self):
        return f"Node({self.value}) -> {self.next!r}"  # Recursive repr

# If node_a.next = node_b and node_b.next = node_a, repr() recurses forever

How to Fix RecursionError

Strategy 1: Add or Correct the Base Case

The first and simplest fix is ensuring a proper base case that is guaranteed to be reached:

# BROKEN — no base case
def factorial_broken(n):
    return n * factorial_broken(n - 1)

# FIXED — base case added
def factorial(n):
    if n <= 1:          # Base case: stop at 1
        return 1
    return n * factorial(n - 1)

print(factorial(5))  # 120 — works correctly

For the broken countdown example, correct the direction of movement toward the base case:

def countdown(n):
    if n == 0:
        print("Done!")
        return
    print(n)
    countdown(n - 1)   # Fixed: now moves TOWARD base case

countdown(5)  # 5, 4, 3, 2, 1, Done!

Strategy 2: Convert Recursion to Iteration

Many recursive functions can be rewritten iteratively, eliminating the recursion limit concern entirely. This is often the most robust solution for production code:

# RECURSIVE VERSION — vulnerable to RecursionError on large n
def factorial_recursive(n):
    if n <= 1:
        return 1
    return n * factorial_recursive(n - 1)

# ITERATIVE VERSION — safe for any input size
def factorial_iterative(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

print(factorial_iterative(1000))  # Works fine, no recursion at all

For tree or graph traversal, use an explicit stack instead of the call stack:

# RECURSIVE DFS — may overflow on deep trees
def dfs_recursive(node, visited=None):
    if visited is None:
        visited = set()
    visited.add(node)
    for neighbor in node.neighbors:
        if neighbor not in visited:
            dfs_recursive(neighbor, visited)
    return visited

# ITERATIVE DFS with explicit stack — no recursion limit
def dfs_iterative(start_node):
    visited = set()
    stack = [start_node]
    while stack:
        node = stack.pop()
        if node not in visited:
            visited.add(node)
            for neighbor in node.neighbors:
                if neighbor not in visited:
                    stack.append(neighbor)
    return visited

Here's another common example — flattening a deeply nested list iteratively:

# RECURSIVE — fails on deeply nested structures
def flatten_recursive(lst):
    result = []
    for item in lst:
        if isinstance(item, list):
            result.extend(flatten_recursive(item))
        else:
            result.append(item)
    return result

# ITERATIVE — handles arbitrary nesting depth
def flatten_iterative(lst):
    result = []
    stack = [lst]
    while stack:
        current = stack.pop()
        for item in reversed(current):  # reversed to preserve order
            if isinstance(item, list):
                stack.append(item)
            else:
                result.append(item)
    return result

deep_list = [1, [2, [3, [4, [5, [6]]]]]]
print(flatten_iterative(deep_list))  # [1, 2, 3, 4, 5, 6]

Strategy 3: Increase the Recursion Limit (Use with Caution)

Python allows you to raise the recursion limit with sys.setrecursionlimit(). This is a quick fix but should be used sparingly — it increases memory usage and can still crash if the recursion goes too deep:

import sys

# Check current limit
print("Current limit:", sys.getrecursionlimit())  # 1000

# Increase to 5000
sys.setrecursionlimit(5000)

# Now deeper recursion works — but be careful
def deep_recursion(n):
    if n == 0:
        return 0
    return 1 + deep_recursion(n - 1)

print(deep_recursion(3000))  # 3000 — works with new limit

# WARNING: Setting the limit too high (e.g., 100000) may cause a segfault
# Only increase to what you actually need, and prefer iterative solutions

Important caveats:

Strategy 4: Tail Recursion Simulation

Python does not officially support tail-call optimization, but you can simulate it manually using a loop wrapper. The idea: rewrite the recursive function so the recursive call is the last operation, then wrap it in a loop that updates arguments instead of making a new call:

# Standard recursive version — uses O(n) stack frames
def gcd_recursive(a, b):
    if b == 0:
        return a
    return gcd_recursive(b, a % b)

# Tail-recursion simulated with a while loop — O(1) stack frames
def gcd_tail_simulated(a, b):
    while b != 0:
        a, b = b, a % b
    return a

print(gcd_tail_simulated(48, 18))  # 6

For more complex functions, use an accumulator pattern inside a loop:

# Recursive sum of list — builds up stack frames
def sum_list_recursive(lst, index=0):
    if index >= len(lst):
        return 0
    return lst[index] + sum_list_recursive(lst, index + 1)

# Simulated tail recursion with while loop
def sum_list_iterative(lst):
    total = 0
    index = 0
    while index < len(lst):
        total += lst[index]
        index += 1
    return total

# For a 10,000-element list, the recursive version would fail;
# the iterative version runs instantly.

Strategy 5: Use Memoization to Reduce Recursion Depth

Memoization caches results of expensive recursive calls. While it doesn't directly prevent RecursionError from infinite recursion, it can dramatically reduce the number of calls needed for algorithms like computing Fibonacci numbers, allowing you to stay well within the recursion limit:

# Naive recursive Fibonacci — O(2^n) calls, hits RecursionError around n=1000
def fib_naive(n):
    if n <= 1:
        return n
    return fib_naive(n - 1) + fib_naive(n - 2)

# Memoized version — O(n) calls, stays within limit
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_memoized(n):
    if n <= 1:
        return n
    return fib_memoized(n - 1) + fib_memoized(n - 2)

print(fib_memoized(500))  # Works perfectly, uses ~500 recursive calls

You can also implement memoization manually with a dictionary:

def fib_manual_memo(n, cache=None):
    if cache is None:
        cache = {}
    if n in cache:
        return cache[n]
    if n <= 1:
        return n
    result = fib_manual_memo(n - 1, cache) + fib_manual_memo(n - 2, cache)
    cache[n] = result
    return result

print(fib_manual_memo(800))  # Works, ~800 calls instead of 2^800

Strategy 6: Fix Circular References in __repr__ and __str__

When RecursionError arises from printing objects with circular references, implement a guard to detect cycles:

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
    
    def __repr__(self):
        # Use a set to track visited nodes during this repr call
        visited = set()
        return self._repr_helper(visited)
    
    def _repr_helper(self, visited):
        if id(self) in visited:
            return "... (circular)"   # Stop recursion on cycle detection
        visited.add(id(self))
        if self.next is None:
            return f"Node({self.value})"
        return f"Node({self.value}) -> {self.next._repr_helper(visited)}"

# Create circular reference
a = Node(1)
b = Node(2)
a.next = b
b.next = a

print(a)  # Node(1) -> Node(2) -> ... (circular) — no RecursionError!

Best Practices for Avoiding RecursionError

def parse_node(data, depth=0, max_depth=500):
    if depth > max_depth:
        raise ValueError(f"Exceeded maximum parse depth of {max_depth}")
    # ... recursive processing with parse_node(child, depth + 1, max_depth)
def trampoline(f, *args):
    result = f(*args)
    while callable(result):
        result = result()
    return result

# Example: factorial expressed as a trampolined function
def factorial_tramp(n, acc=1):
    if n <= 1:
        return acc
    return lambda: factorial_tramp(n - 1, acc * n)

print(trampoline(factorial_tramp, 5000))  # No RecursionError

Conclusion

RecursionError is Python's safeguard against runaway recursion and stack overflow. While the default recursion limit of 1000 calls is generous for most algorithms, it becomes a problem when base cases are missing, logic errors prevent termination, or data structures are unexpectedly deep. The most reliable fix is to convert recursive algorithms to iterative ones using explicit stacks or loops — this eliminates the recursion limit entirely and often improves performance. When recursion is truly the clearest approach, ensure your base case is correct and reachable, apply memoization to reduce call count, and consider raising the recursion limit only with careful bounds and fallbacks. By understanding the root cause and applying the right strategy, you can turn a crashing RecursionError into robust, production-ready code.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles