Skip to content
Happy Programming Guide
Start learning
Python

Python Recursion Error: Fixing Maximum Depth Exceeded

RecursionError means your function called itself too many times. Here is how to find the missing base case, when to raise the limit, and how to convert recursion to a loop.

A person working on a laptop

RecursionError: maximum recursion depth exceeded means a function called itself about a thousand times without ever stopping. Ninety per cent of the time this is a bug — a missing or unreachable base case — and raising the recursion limit only turns the error into a crash. This guide covers how to tell the two situations apart and what to do in each.

What the error means#

Python
def countdown(n):
    print(n)
    countdown(n - 1)


countdown(5)
Output
RecursionError: maximum recursion depth exceeded while calling a Python object

Every function call takes a slot on the call stack. Python caps that at around 1000 by default so a runaway function fails with a clear error instead of exhausting memory and taking the process down. The limit is a safety net, not the problem.

Cause 1: no base case#

The example above never stops, because nothing tells it to. Add the condition that ends the recursion:

Python
def countdown(n):
    if n <= 0:        # base case
        return
    print(n)
    countdown(n - 1)  # recursive case, moving towards the base

Every correct recursive function has two parts: a base case that returns without recursing, and a recursive case that moves measurably closer to it. If you cannot point at both in your function, that is the bug.

Cause 2: a base case that is never reached#

This one is harder to spot, because a base case exists:

Python
def countdown(n):
    if n == 0:
        return
    print(n)
    countdown(n - 2)


countdown(5)   # 5, 3, 1, -1, -3, ... never exactly 0

Starting from an odd number, n skips straight past zero. Use a boundary rather than an equality test:

Python
if n <= 0:
    return

The same trap catches float arithmetic, where rounding means a value never lands exactly on the target. Prefer <= and >= over == in any recursive stopping condition.

Cause 3: the value is not actually shrinking#

Python
def flatten(items):
    result = []
    for item in items:
        if isinstance(item, list):
            result.extend(flatten(item))
        else:
            result.append(item)
    return result


data = [1, [2, 3]]
data.append(data)      # the list now contains itself
flatten(data)          # RecursionError

Nothing is wrong with the function; the data has a cycle. Track what you have already seen:

Python
def flatten(items, seen=None):
    seen = set() if seen is None else seen
    if id(items) in seen:
        return []
    seen.add(id(items))

    result = []
    for item in items:
        if isinstance(item, list):
            result.extend(flatten(item, seen))
        else:
            result.append(item)
    return result

The same pattern applies to graphs, linked structures and folder trees containing symbolic links.

How to debug it#

Print the argument on the way in. The pattern tells you which cause you have:

Python
def countdown(n, depth=0):
    print("  " * min(depth, 20), "n =", n)
    if n <= 0:
        return
    countdown(n - 1, depth + 1)

If the value never changes, it is cause 3. If it changes but sails past the stopping point, it is cause 2. If there is no stopping point in the code at all, it is cause 1.

You can also check how deep you currently are:

Python
import sys

print(sys.getrecursionlimit())   # 1000 by default
print(len(sys.stack()) if hasattr(sys, "stack") else "n/a")

When raising the limit is the right answer#

Occasionally the recursion is genuinely correct and genuinely deep — parsing a deeply nested document, or walking a long linked list.

Python
import sys

sys.setrecursionlimit(10_000)

Do this only when you have confirmed the recursion terminates. The limit protects a real resource: each Python frame also uses C stack space, and exceeding that gives you a segmentation fault with no traceback at all rather than a clean exception. Raising it to a million is not a fix, it is a delayed crash.

Converting recursion to a loop#

Linear recursion — one call per step — always converts to a simple loop:

Python
# Recursive
def factorial(n):
    return 1 if n <= 1 else n * factorial(n - 1)


# Iterative - no stack limit
def factorial(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

Tree recursion converts using an explicit stack, which is the same thing Python was doing for you:

Python
def walk(root):
    """Visit every node without recursion."""
    stack = [root]
    seen = set()
    while stack:
        node = stack.pop()
        if id(node) in seen:
            continue
        seen.add(id(node))
        yield node
        stack.extend(node.get("children", []))

Swap the stack.pop() for a deque.popleft() and you get breadth-first order instead.

When recursion is correct but slow#

Naive Fibonacci recomputes the same values enormously many times:

Python
from functools import lru_cache


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


print(fib(100))

One decorator turns an exponential function into a linear one by remembering results. Note this fixes slowness, not depth — fib(5000) will still hit the recursion limit, because the chain of calls is still 5000 deep.

Questions people ask#

Why is the default limit 1000?

It is a conservative value chosen so that hitting it raises a Python exception before the underlying C stack runs out. The real constraint is memory per frame, which varies by platform, so Python errs on the safe side.

Does Python optimise tail recursion?

No, deliberately. Guido van Rossum has explained that keeping full stack traces for debugging was judged more valuable. That is why deep recursion in Python should generally be rewritten as a loop.

Can I catch RecursionError?

You can, with except RecursionError, but be careful: the stack is nearly full at that moment, so the handler itself has very little room to work in. Fixing the cause is far better than catching it.

Why do I get RecursionError with no recursion in my code?

Usually a property or a __getattr__ that refers to itself — for example a property named value whose body reads self.value. The traceback repeats the same two or three lines, which is the giveaway.

Where to go next#

Python functions explained, including return valuesRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *