Your function works on small examples, but on a real input Python prints page after page of the same line and finishes with RecursionError: maximum recursion depth exceeded. Recursion is elegant when it works and bewildering when it does not, because the failure looks like the computer simply gave up. In a sense, it did: Python limits how many nested calls can stack up, to protect itself. This guide explains what the limit protects, the three bugs that cause the error, and when to fix the recursion versus rewriting the function as a loop.
Quick Answer
A RecursionError means your function called itself so many times that it hit Python’s safety limit, about 1,000 nested calls by default. The usual cause is a missing or unreachable base case, or a recursive call that does not move the input closer to that base case. Fix the logic so every call makes progress toward stopping. Raising the limit with sys.setrecursionlimit() is a last resort for genuinely deep but correct recursion, not a cure for a bug.
How Recursion Uses the Call Stack
Every function call adds a frame to the call stack, holding that call’s local variables and its place to return to. A recursive function adds a new frame each time it calls itself, and frames only come off the stack when calls finish. If the recursion never stops, frames pile up without limit, consuming memory until the interpreter itself is at risk. Python therefore enforces a ceiling — check yours with sys.getrecursionlimit(), typically 1000 — and raises RecursionError before real damage occurs. A runaway loop repeats in place, but recursion consumes more stack with every step, which is why Python guards it. If you met the loop version first, our guide on stopping an infinite loop safely covers that sibling problem.
The Three Bugs Behind Almost Every RecursionError
1. There is no base case
Every recursive function needs at least one condition under which it returns directly without calling itself. Forget it, and even a perfect-looking function recurses forever. Here is the shape of the bug: a countdown function that prints and calls itself with n - 1 but never tests whether n reached zero. Adding if n <= 0: return at the top completes it.
2. The base case exists but is never reached
Sometimes the stopping test exists yet the calls move away from it, or step over it. Counting down by 2 while testing for exactly 0 never lands on 0 when starting from an odd number. Subtracting a value that could be zero, or recursing with the same argument by mistake (passing n instead of n - 1), produces the same endless descent. Compare the test and the step: each call must bring the argument strictly closer to the base case.
3. The function calls itself by accident
A subtle trap: defining a property or method with the same name as an attribute it uses, or a wrapper that calls the original name instead of the wrapped function, creates recursion you never wrote deliberately. Another classic is mutual recursion with a broken hand-off, where function A calls B and B calls A with unchanged arguments. The traceback exposes these: read the repeating frames and ask of each, “was this call intended?” The official exception reference describes the error, and the traceback’s repeated lines are your map.
A Step-by-Step Debugging Routine
- Reproduce with the smallest failing input. If a list function fails at length 500, test lengths 1, 2, 3, and 10. Small cases make the call pattern visible.
- Print the argument at each entry. Add a temporary
print("call with", n)as the first line of the function. Correct recursion shows arguments marching toward the base case; the bug shows them frozen, growing, or oscillating. - Verify the base case by calling it directly. Invoke the function with the exact base-case input and confirm it returns immediately without recursing.
- Check the recursive call’s argument expression. Read it character by character. A surprising share of these bugs are
nwritten wheren - 1was meant, or the wrong variable passed entirely. - Remove the prints and retest up the sizes. Confirm small, medium, and original inputs all finish.
When the Recursion Is Correct but Deep
Some problems are legitimately deep: walking a folder tree with very nested paths, parsing deeply nested data, or traversing a graph shaped like a long chain. If the logic is provably correct and inputs are simply large, you have two honest options. The safer one is rewriting the algorithm iteratively with an explicit list as your own stack, which removes the limit entirely. The quicker one is raising the ceiling with sys.setrecursionlimit(10000) near the program’s start, accepting that an excessively deep stack can crash the interpreter outright instead of raising a tidy error. Treat the raised-limit route as a measured risk for code you have verified, never as a first response to the error.
Common Mistakes to Avoid
- Raising the recursion limit as the first fix. If the base case is broken, a bigger limit only delays the error and risks a hard crash.
- Recursing on data without shrinking it. List functions should pass a smaller slice or an index that advances; tree functions should move to a child. Passing the same data unchanged guarantees the error.
- Hiding the error with try/except RecursionError. Catching it does not unwind your logic problem, and partial results computed mid-collapse are rarely trustworthy.
- Using recursion for huge linear jobs in Python. Summing a million-element list recursively is the wrong tool even with a raised limit; a simple loop is faster, clearer, and unbounded.
Recursion Versus Loops: Choosing on Purpose
Neither style is universally better; they suit different problem shapes. Recursion shines when the data is itself recursive — folders containing folders, a family tree, a document with nested sections, a divide-and-conquer algorithm like binary search or quicksort — because the code can mirror the data’s shape and stay short and clear. Loops shine for flat, sequential work: processing every row of a file, counting, accumulating totals. When a recursive solution’s depth depends on how much data arrives, treat that as a design smell in Python and consider the iterative form early, because data volumes grow while interpreter limits do not. Learning to translate between the two styles — which mostly means managing your own stack list when you go iterative — is one of the skills that separates someone who can write recursion from someone who can choose it wisely.
Conclusion
RecursionError means the call stack filled because the recursion never wound down. Restore a reachable base case, make every call move measurably toward it, and debug with printed arguments on the smallest failing input. Reserve setrecursionlimit for verified-correct deep recursion, and prefer an iterative rewrite when depth is truly unbounded.
Frequently Asked Questions
What is the default recursion limit in Python?
Usually 1,000 frames, which you can confirm with sys.getrecursionlimit(). Remember that your function’s frames share the stack with the code that called it, so the practical depth available to your recursion is somewhat less than the headline number.
Why did my code work in testing but fail on real data?
Depth scales with input size for many recursive algorithms. Tests with ten items need ten frames; production data with ten thousand items needs ten thousand. This is the signature of correct logic hitting a real limit, which is when an iterative rewrite or a carefully raised limit becomes a legitimate discussion.
Can functools.lru_cache fix a RecursionError?
No. Memoisation avoids repeating work for identical arguments, which speeds up overlapping recursion like naive Fibonacci, but it does not reduce the depth of a single call chain. A function recursing a thousand levels deep still errors, cache or not.
How do I convert a recursive function to a loop?
Replace the call stack with your own list used as a stack: push the starting item, then repeatedly pop an item, process it, and push its “next steps” until the list is empty. This mirrors exactly what recursion did, but the stack lives in ordinary memory and can grow far beyond the interpreter’s frame limit.
Why does the traceback repeat the same line hundreds of times?
Because each repeated line is one live frame of the same function calling itself. Python trims the display in newer versions with a note about how many frames were repeated, but the meaning is unchanged: you are looking at the recursion itself, frozen at the moment the limit was hit.


Leave a Reply