RecursionError: maximum recursion depth exceeded (tested)

RecursionError: maximum recursion depth exceeded means a function called itself (directly or indirectly) more times than Python's recursion limit allows, almost always because it's missing a base case that would stop the recursion, or the base case is unreachable for the input given.

1. The default limit and five ways to hit it

Python's default recursion limit on this interpreter (3.13.5) is 1000 stack frames, checked with sys.getrecursionlimit(). We triggered the same error five different ways; the message text is identical across all of them in this Python version:

Code patternExact error
def f(): f() — no base case at allRecursionError: maximum recursion depth exceeded
def fact(n): return n * fact(n-1) — missing if n == 0: return 1RecursionError: maximum recursion depth exceeded
Two functions calling each other (a() calls b() calls a()), neither has a base caseRecursionError: maximum recursion depth exceeded
A class whose __eq__ compares self == other instead of a real fieldRecursionError: maximum recursion depth exceeded
A class whose __repr__ calls repr(self) instead of formatting fieldsRecursionError: maximum recursion depth exceeded

Older Python versions and many existing tutorials show longer messages with a suffix, like "...exceeded while calling a Python object" or "...exceeded in comparison". On this interpreter (3.13.5) every case above produced the same plain message with no suffix — if you're on an older Python and see a suffixed version, it's the same error, just older wording.

2. The real bug is almost always a missing or unreachable base case

A working recursive function needs a condition that stops it without calling itself again:

# broken: no base case, recurses forever
def fact(n):
    return n * fact(n - 1)

# fixed: base case stops the recursion at n == 0
def fact(n):
    if n == 0:
        return 1
    return n * fact(n - 1)

A base case that exists but is unreachable causes the same error — for example checking if n == 0 but calling with a negative number, which counts down past zero forever (-1, -2, -3, ...), or a float input that never exactly equals an integer base case.

3. Mutual recursion hides the problem across two functions

The same missing-base-case bug, split across two functions, is harder to spot because neither one looks recursive on its own:

def is_even(n):
    if n == 0: return True
    return is_odd(n - 1)
def is_odd(n):
    if n == 0: return False      # base case present here
    return is_even(n - 1)        # but called with n < 0, it never hits either base case

Fix: trace the actual call chain, not just one function, and check that every path through both functions terminates.

4. Dunder methods can recurse invisibly

A subtle variant: implementing __eq__, __repr__, or __str__ in a way that calls itself instead of a real attribute. This is easy to miss because the recursive call doesn't look like a function call — it's triggered by ==, print(), or f-strings:

class Bad:
    def __eq__(self, other):
        return self == other   # calls __eq__ again, forever
>>> Bad() == Bad()
RecursionError: maximum recursion depth exceeded

Fix: compare specific attributes (self.value == other.value), never self against other directly inside __eq__ itself.

5. Deep-but-legitimate recursion: raise the limit, or don't

A genuinely large but finite recursion (deep tree traversal, unmemoized Fibonacci) can also exceed the default 1000-frame limit even with a correct base case. sys.setrecursionlimit(n) raises the ceiling, but it does not create more actual C stack space — set it too high and you risk a real segmentation fault instead of a catchable Python exception, which is strictly worse.

import sys
sys.setrecursionlimit(5000)  # raises the ceiling, doesn't add real stack space

For deep-but-legitimate recursion, prefer converting to an iterative loop with an explicit stack/queue, or add memoization (functools.lru_cache) if the same calls repeat, which often reduces both the depth and the total work.

6. You can catch it, but reconsider doing so inside the recursive function itself

RecursionError is a regular exception, catchable with except RecursionError:. But catching it inside the same recursive function re-triggers the handler at every stack level as the exception unwinds, which we confirmed: a try/except RecursionError: raise wrapped around the recursive call printed its handler once per unwound frame, not once total. Catch it at the top-level caller instead, not inside the recursive function.

7. Fix checklist

Frequently asked questions

What does RecursionError: maximum recursion depth exceeded mean?

A function called itself, directly or through other functions, more times than Python's recursion limit (1000 by default) allows without ever hitting a condition that stops the recursion. It is almost always caused by a missing or unreachable base case.

Why doesn't my function's base case stop the recursion?

The base case may be unreachable for the actual inputs reaching it - for example checking n == 0 while calling with negative numbers, which count past zero forever, or using == on floats that never land exactly on the base value.

Is it safe to just raise the recursion limit with sys.setrecursionlimit()?

Only a little, and not as a real fix. It raises Python's own counter but does not add actual C stack space, so setting it too high risks a hard segmentation fault instead of a catchable RecursionError. Prefer an iterative rewrite for genuinely deep recursion.

Can a class's __eq__ or __repr__ method cause this error?

Yes. If __eq__ compares self == other instead of specific attributes, or __repr__ calls repr(self) instead of formatting fields, each call triggers another call to the same method, recursing until the limit is hit. We reproduced both on Python 3.13.5.

Should I catch RecursionError inside the recursive function itself?

No - we tested this and the except block re-runs once per stack frame as the exception unwinds, not once total, producing repeated output. Catch it once at the outermost, non-recursive caller instead.

Written with AI assistance. Measured on 2026-10-03 with Python 3.13.5 on Linux using plain python3 one-liners. Every error below is copy-pasted output, not paraphrased. PlainHeaders is our extension; Python is free.