Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
This error means Python kept nesting calls (or other call-like operations) until it reached its recursion safety limit. The repeated call is not always an obvious function calling itself: properties, attribute hooks, decorators, callbacks, special methods, and cyclic data can create the same loop. The reliable fix is to find that cycle, make execution progress toward a stopping condition, or replace recursion with iteration. Increasing the limit is only appropriate for known, finite, unusually deep recursion.
What the message means
RecursionError is a RuntimeError raised when the interpreter detects that the maximum recursion depth has been exceeded. Python keeps this guard partly to prevent uncontrolled Python calls from exhausting the underlying C stack and crashing the process. See the official exception documentation.
The suffix “while calling a Python object” is context from CPython’s call machinery. It tells you where recursion was noticed, not which line originally created the loop. A function may be calling itself indirectly, or an operation such as obj(...), obj.attr, repr(obj), or len(obj) may invoke user-defined code repeatedly.
Start with the smallest example
def f():
f()
f()
This function never changes state or moves toward termination. A valid recursive function needs a reachable base case, a recursive step, and measurable progress:
#1 Best Overall
def countdown(n):
if n <= 0: # base case
return
print(n)
countdown(n - 1) # progress toward the base case
countdown(3)
A base case that can never be reached is effectively no base case. If there are multiple recursive branches, each branch must eventually terminate.
Read the traceback as a call cycle
- Scroll to the bottom and confirm the final exception.
- Examine the repeated frames immediately above it.
- Look for the same line repeating, or for two or more functions alternating.
- Find the first transition that enters the repeating cycle; that is often where the missing condition lives.
File "example.py", line 4, in first
second()
File "example.py", line 8, in second
first()
File "example.py", line 4, in first
second()
...
RecursionError: maximum recursion depth exceeded while calling a Python object
The traceback can be truncated, so do not focus only on its final repeated line. Draw the call chain and identify the smallest cycle. A temporary depth guard can expose the failing input earlier:
def walk(node, depth=0):
if depth > 100:
raise RuntimeError("unexpected recursion depth")
# ...
For controlled logging, avoid formatting an object that may have a recursive __repr__ or __str__:
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →print("type=", type(node).__name__, "id=", id(node))
Using repr(node) or an f-string containing the whole object can trigger the same bug you are trying to diagnose.
Common hidden causes
1. Mutual recursion
def parse(value):
return validate(value)
def validate(value):
return parse(value)
No function visibly calls itself, but the pair has no terminating state. Decide which function owns the stopping condition and add a decreasing measure, a state transition, or a result that breaks the cycle.
Rank #2
2. A property calls itself
class User:
@property
def name(self):
return self.name # invokes the getter again
@name.setter
def name(self, value):
self.name = value # invokes the setter again
Store the value in a separate backing attribute:
class User:
def __init__(self, name):
self.name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
The leading underscore is a convention, not a security boundary. Property and descriptor behavior is described in Python’s descriptor HOWTO.
3. Recursive attribute hooks
Inside __getattribute__, ordinary attribute access goes through __getattribute__ again:
class Config:
def __getattribute__(self, name):
return self.settings[name] # recursion
Bypass the override for internal access:
class Config:
def __getattribute__(self, name):
if name == "settings":
return object.__getattribute__(self, name)
settings = object.__getattribute__(self, "settings")
if name in settings:
return settings[name]
return object.__getattribute__(self, name)
A similar mistake with __getattr__ is asking for the same missing attribute:
class Settings:
def __getattr__(self, name):
return getattr(self, name) # asks for the missing name again
Use a different storage location or raise AttributeError. See the attribute-access documentation.
4. Recursive __repr__, __str__, or logging
class Node:
def __repr__(self):
return f"Node({self})"
Formatting self invokes representation methods again. Parent/child objects that refer to each other can also recurse while being displayed. Prefer a cycle-safe representation:
class Node:
def __repr__(self):
return f"Node(value={self.value!r}, id={id(self)})"
print(obj), f-strings, exception formatting, container display, and logging may implicitly call __str__ or __repr__. During diagnosis, print only the type and identity. Python documents these hooks under __repr__ and __str__.
Free tools Windows power users keep installed
One-click scans. No signup required.
5. A callable object or wrapper calls itself
class Repeater:
def __call__(self, value):
return self(value) # calls the same instance
Call the intended operation instead:
class Doubler:
def __call__(self, value):
return value * 2
Decorator wrappers have the same trap:
def log_calls(func):
def wrapper(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
return wrapper
Returning wrapper(*args, **kwargs) from inside that wrapper recurses. Keep a reference to the original function.
6. Callbacks and observers re-trigger themselves
A GUI callback, retry hook, signal handler, ORM observer, or property notification can synchronously trigger the event that invoked it:
def callback():
trigger(callback)
def trigger(fn):
fn()
Check whether a callback changes the state that caused the callback, whether a setter notifies an observer that writes the same property, or whether a retry path has a bounded attempt count.
7. Cyclic graphs mistaken for trees
A traversal that assumes a tree will not terminate on a graph such as A → B → C → A:
def walk(node, seen=None):
if seen is None:
seen = set()
marker = id(node)
if marker in seen:
return
seen.add(marker)
for child in node.children:
walk(child, seen)
Use id(node) when object identity is the intended criterion; a stable application-level node ID may be clearer. A visited set also avoids repeated shared subtrees, but that is a performance choice rather than proof of a cycle.
8. Overloaded operators and conversions
Inspect methods such as __eq__, __lt__, __iter__, __len__, __bool__, __hash__, and serializers. For example:
class Value:
def __eq__(self, other):
return self == other # invokes __eq__ again
Likewise, __iter__ should return an iterator over internal data, not iter(self); __bool__ should not test if self.
Check the recursion limit—do not confuse it with the fix
import sys
print(sys.getrecursionlimit())
sys.getrecursionlimit() reports the current interpreter’s limit; a value around 1,000 is common in CPython, but it is not universal. The limit is distinct from your algorithm’s required depth and from the operating system’s underlying C-stack capacity.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteYou can temporarily change it:
import sys
sys.setrecursionlimit(3000)
Do this only when the recursion is finite, bounded, intentional, and tested on the deployment platform. The Python documentation warns that an excessively high value can crash the interpreter; setting a value below the current depth raises RecursionError. Raising the limit cannot make an infinite cycle terminate and may merely delay diagnosis.
Best Value
When iteration is safer
For deep linear work, an explicit loop avoids consuming one Python frame per step:
def factorial(n):
result = 1
for value in range(2, n + 1):
result *= value
return result
For a tree or graph, use an explicit stack while retaining cycle detection and traversal order:
def walk(root):
stack = [root]
seen = set()
while stack:
node = stack.pop()
marker = id(node)
if marker in seen:
continue
seen.add(marker)
stack.extend(reversed(node.children))
Recursion remains clear for naturally hierarchical algorithms, divide-and-conquer code, and parsers whose depth is small and provably bounded. Python does not generally eliminate recursive frames through tail-call optimization.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →A practical repair checklist
- Confirm the final exception and inspect repeated traceback frames.
- Look for direct self-calls and alternating function or method calls.
- Verify that every recursive path makes progress toward a reachable base case.
- Check property backing fields and attribute hooks.
- Temporarily disable decorators, callbacks, observers, and overloaded methods.
- Avoid printing suspect objects; log type names and
id()values. - Add a visited set for graph-like data and a temporary depth guard.
- Minimize the input to the smallest reproducer.
- Prefer an explicit loop or stack for very deep input.
- Only then consider a carefully tested recursion-limit increase.
If a third-party library is responsible, create a minimal reproducer, record the Python implementation and platform, and inspect the library’s version-specific issue tracker or patch the underlying cycle rather than masking it with a larger limit.
Quick decision tree
Does one function repeat in the traceback?
Yes → Check its base case and progress.
No → Do functions alternate?
Yes → Break the mutual cycle.
No → Inspect properties, descriptors, decorators,
callbacks, and special methods.
Is the input finite but unusually deep?
Yes → Prefer iteration; cautiously consider a higher limit.
Is the input cyclic?
Yes → Track visited objects.
An import cycle is a different problem: it more often produces ImportError, partially initialized modules, or missing attributes than this exact recursion exception. First establish that the traceback really contains repeated Python calls.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

