Python RecursionError: fix the cause before raising the limit
Fix maximum recursion depth exceeded in Python. Distinguish a missing stop condition, a self-calling property, a cycle, and a genuinely deep input.
RecursionError means Python exceeded its recursion-depth limit. Inspect the repeated traceback frames: the cause may be a missing stopping condition, calls that make no progress, an accidental call cycle, or a valid input that is too deep. Raising the limit does not fix the first three.
A worker can ask another worker to finish a smaller part of a job. If every worker asks for the same job again, nobody finishes. Even when the jobs really get smaller, an extremely long chain can exceed the space available for waiting workers.
Start at the bottom of the traceback, then look upward for the repeating function names and line numbers. The question is not simply, “How many calls did Python allow?” It is, “What is supposed to change between these calls so that one of them eventually finishes?”
Read the repeating part of the traceback#
If one line repeats, inspect its arguments and its base case—the condition that returns without another recursive call. If two or more functions alternate, inspect the whole cycle. A property getter, formatter or equality method can create recursion without an obviously recursive algorithm.
Reduce the input to the smallest case that still fails. Add a temporary diagnostic for the changing argument, not an unrestricted dump of the entire object: printing an object can itself call a broken __repr__ or __str__. Preserve the full traceback in your debugging environment, but remove private values before sharing it.
Python documents RecursionError as exceeding the interpreter’s recursion-depth limit. That identifies the failure boundary; it does not tell you which of these causes crossed it.
Try it: read the repeated call
Choose an illustrative traceback pattern before changing Python’s recursion limit.
Make the stop condition reachable#
A decreasing number is not sufficient by itself. A function that stops only at n == 0 but starts at -1 and keeps subtracting one will never reach zero. A correct repair needs both a stopping condition and an input domain in which each call approaches it.
This complete Python example accepts only nonnegative integers. It makes the recursive version’s progress visible, then shows a loop which does not consume a call frame for each step.
def checked_n(n: int) -> None:
if type(n) is not int:
raise TypeError("n must be an integer")
if n < 0:
raise ValueError("n must be nonnegative")
def factorial_recursive(n: int) -> int:
checked_n(n)
if n == 0:
return 1
return n * factorial_recursive(n - 1)
def factorial_iterative(n: int) -> int:
checked_n(n)
result = 1
for value in range(2, n + 1):
result *= value
return result
assert factorial_recursive(0) == 1
assert factorial_recursive(5) == 120
assert factorial_iterative(5) == 120
assert factorial_iterative(20) == 2432902008176640000
for function in (factorial_recursive, factorial_iterative):
try:
function(-1)
except ValueError:
pass
else:
raise AssertionError("negative input must be rejected")
The recursive example is for understanding small inputs, not a promise to support arbitrarily large n. The loop avoids depth growth but still takes time and stores a growing integer. Replacing recursion changes one resource limit, not every resource limit. The recursion walkthrough explains how the waiting calls resume.
Check properties and cyclic data#
A getter named name that returns self.name invokes that same getter again. Store the value under a separate attribute instead:
class Contact:
def __init__(self, name: str) -> None:
self._name = name
@property
def name(self) -> str:
return self._name
assert Contact("Mira").name == "Mira"
For graph-like data, following every child can revisit a previous object indefinitely. Decide what a repeated node means for your operation. A reachability traversal can skip already visited identities. A tree validator may instead need to reject the input. A cycle detector may need to distinguish nodes on the current path from nodes visited on a completed path. Adding a set without deciding the contract can suppress valid work or conceal malformed data.
When the input really is deep#
A correct algorithm can still exceed the limit on a sufficiently long chain. Inspect the active limit with sys.getrecursionlimit() rather than assuming a fixed value. Python’s sys documentation warns that the supported maximum is platform-dependent and that setting a limit too high can cause a crash.
For a tree or graph whose depth you do not control, an explicit stack is usually easier to bound and observe. The binary-tree diameter implementation shows that conversion and states its real memory cost. For hostile or merely oversized inputs, also impose an application-appropriate size/work limit.
Do not surround the entire operation with except RecursionError: pass. That turns a failed computation into an apparently successful one. Handle failure only where the caller can take a deliberate next action, preserve its context, and test the original failing input after the repair. The try/except guide explains where that boundary belongs.
Questions people also ask
2 QUESTIONSShould I use sys.setrecursionlimit to fix RecursionError?
Not as the first repair. Prove that the calls terminate, measure the required depth and check the runtime's constraints. A larger limit can hide a bug and, depending on the platform and call path, can risk a crash. A loop or explicit traversal stack is often a better fit for unbounded depth.
What does 'while calling a Python object' mean?
It describes the operation where the limit was detected, not the underlying cause. Follow the repeated frames to find the call chain. The same logic error may produce different message wording depending on the operations involved.