RecursionError

Python caps stack depth (1000 frames by default) instead of crashing; hitting the cap nearly always means a base case your input never reaches.

InheritsBaseException›Exception›RuntimeError›RecursionError
Runtime exceptionPython 3.5+Live demo
RecursionError(*args)
Raised by
recursion with no reachable base case; self-referencing __getattr__/property; very deep nested data
Message
maximum recursion depth exceeded
Quick fix
fix the base case, or rewrite as a loop
Watch out
raising the limit only moves the crash

Demo

Live evaluation
Count digits by recursing on n // 10 until n == 0. Try a negative number.
Try:
Inputs
nintnumber to measure
Code
def digits(n):
    if n == 0:
        return 0
    return 1 + digits(n // 10)
digits(2024)
Result
4

Positive numbers shrink to 0 in a few steps. Negative ones never do: // rounds toward minus infinity, so -5 // 10 is -1 and -1 // 10 is -1 again — the function calls itself forever until the 1000-frame limit stops it. The base case exists; the input just cannot reach it. The fix is in the pitfalls below.

Constructor

NameTypeRequiredDescription
*argsobjectnoThe message. The interpreter passes one string; str(e) is that string.

Attributes

AttributeTypeMeaning
argstupleThe message tuple — e.g. ('maximum recursion depth exceeded',).
__traceback__tracebackVery long: the same frame repeated. Python collapses it to "[Previous line repeated N more times]" when printing.

Common patterns

Rewrite as a loop
Tail-style recursion becomes a while loop with no depth limit at all.
def digits(n):
    n = abs(n)
    count = 1
    while n >= 10:
        n //= 10
        count += 1
    return count
Explicit stack for tree walks
Deep trees (nested JSON, file trees, linked structures) walked with your own list instead of the call stack.
def walk(root):
    stack = [root]
    while stack:
        node = stack.pop()
        yield node
        stack.extend(node.children)
Raise the limit (with care)
For legitimately deep but bounded recursion only. Too high a value can crash the process with a real C stack overflow instead of a clean exception.
import sys
sys.setrecursionlimit(5000)

Examples

1. No base case at all
def countdown(n): return countdown(n - 1) countdown(3)
Returns
RecursionError: maximum recursion depth exceeded
2. The default limit
import sys sys.getrecursionlimit()
Returns
1000
3. Property that reads itself
class User: @property def name(self): return self.name # should be self._name User().name
Returns
RecursionError: maximum recursion depth exceeded
4. Deeply nested data: repr
data = [] for _ in range(100_000): data = [data] repr(data)
Returns
RecursionError: maximum recursion depth exceeded while getting the repr of an object
5. Deeply nested data: json
import json data = [] for _ in range(100_000): data = [data] json.dumps(data)
Returns
RecursionError: maximum recursion depth exceeded while encoding a JSON object
6. It is a RuntimeError
issubclass(RecursionError, RuntimeError)
Returns
True
7. Catch it and carry on
def f(): return f() try: f() except RecursionError as e: msg = str(e) msg
Returns
'maximum recursion depth exceeded'

Pitfalls

1. A base case the input can skip
n == 0 is never hit for negative n with floor division (or for odd n when stepping by 2). Make the base case cover everything the input can be.
n == 0
def digits(n):
    if n == 0:
        return 0
    return 1 + digits(n // 10)
digits(-5)
RecursionError: maximum recursion depth exceeded
Normalize first
def digits(n):
    n = abs(n)
    if n < 10:
        return 1
    return 1 + digits(n // 10)
digits(-5)
1
2. __getattr__ that touches a missing attribute
__getattr__ runs for every failed lookup — including the ones it makes itself. Read from self.__dict__ directly inside it.
self.data
class Config:
    def __getattr__(self, name):
        return self.data[name]
Config().port
RecursionError: maximum recursion depth exceeded
self.__dict__
class Config:
    def __getattr__(self, name):
        data = self.__dict__.get('data', {})
        if name in data:
            return data[name]
        raise AttributeError(name)
c = Config()
c.data = {'port': 80}
c.port
80
3. __setattr__ that assigns through itself
self.x = v inside __setattr__ calls __setattr__ again. Delegate to object.__setattr__ (or super()).
setattr(self, …)
class Point:
    def __init__(self):
        self.x = 0
    def __setattr__(self, name, value):
        setattr(self, name, value)
Point()
RecursionError: maximum recursion depth exceeded
super().__setattr__
class Point:
    def __init__(self):
        self.x = 0
    def __setattr__(self, name, value):
        super().__setattr__(name, value)
Point().x
0

When to use

Use it
  • Catching it at a boundary to report "input too deeply nested" for untrusted data
  • Recognising it in a traceback as "this recursion has no reachable end"
Reach for something else
  • Using the exception as control flow for normal inputs
  • Silencing it with sys.setrecursionlimit(10**6) — the process can segfault instead
  • Recursion over unbounded input (linked lists, user-provided nesting) → loop with an explicit stack

Notes

CPython impl
Since 3.12 sys.getrecursionlimit() applies only to Python code; C-level recursion (repr, json, comparisons of nested data) is guarded by a separate mechanism
Catch via
except RuntimeError also catches it (RecursionError subclasses RuntimeError)
No TCO
CPython does not eliminate tail calls, so return f(n - 1) still uses one frame per step

FAQ

Find the frame that repeats in the traceback and ask why the base case is not reached for this input: a missing base case, a step that does not move toward it (negative numbers, odd numbers stepping by 2), or an attribute hook (__getattr__, __setattr__, a property) that calls itself. If the recursion is genuinely deep but finite — walking a big tree or deeply nested data — rewrite it as a loop with an explicit stack.

History

3.5
Added. Previously a plain RuntimeError was raised.