Recursion — Visual Learning

Base Case —
The one line that decides if recursion ever stops.

Every recursive function needs exactly two things: a base case that returns directly with no further recursion, and a recursive case that always moves closer to that base case. Miss either one and the function recurses forever — until Python's stack limit ends it for you. Watch a broken factorial hit that limit on LearnBug, frame by frame.

2Required parts
~1000Python's call depth limit
PythonLanguage

What is a Base Case?

The condition inside a recursive function that returns a value directly, without calling the function again. It's the only thing that stops the recursion — every recursive call must eventually reach it. The recursive case is everything else: it calls the function again, always with an input that's strictly closer to the base case.

Why visualization helps

A missing or wrong base case doesn't crash immediately — it looks completely normal for the first several hundred calls. LearnBug lets you watch the call stack climb past frame 900, 950, 999 and see the exact moment Python gives up and raises RecursionError, instead of just staring at a traceback after the fact.

LearnBug — call stack climbing toward the recursion limit
🖼 Add a LearnBug screenshot here
Correct vs Broken

Same function, one line different

Correct — has a reachable base case

if n == 1: return 1 — every recursive call passes n - 1, so n strictly decreases and is guaranteed to hit 1 eventually.

Terminates

Broken — no base case at all

The base case check is missing entirely, so every call always recurses — n decreases toward 1, passes right through it, and keeps going into negative numbers forever.

RecursionError

Also broken — a base case that's never reached

If the recursive case doesn't move toward the base case (e.g. calling factorial(n) instead of factorial(n-1)), the base case exists but is unreachable.

Unreachable base case
Walkthrough

broken_factorial(5) — no base case, watch it run away

1

broken_factorial(5) calls broken_factorial(4), (3), (2), (1) — looks completely normal so far

For the first several calls, this is indistinguishable from the correct version — every recursive call is legitimately getting closer to what should be the base case.

5
4
3
2
1
Stack depth: 5  |  Nothing looks wrong yet
2

There's no `if n == 1` check — n=1 just calls broken_factorial(0)

This is the exact moment the bug matters. The correct version would stop here. This one has no check at all, so it keeps recursing straight through the value that should have stopped it.

1should stop!
0
-1
n keeps decreasing past 1 into negative numbers — no base case exists to catch it
3

Stack depth climbs past 900, 950, 999 — every frame identical, none of them returning

The function keeps calling itself with ever-more-negative n. Nothing about any individual call looks different from the last one — the frames are piling up with no end in sight.

-995
-996
-997
Stack depth: ~998  |  Approaching Python's ~1000 frame limit
4

Frame ~1000: Python refuses to push another one — RecursionError

Python doesn't detect "this will never stop" — it just runs out of allowed stack depth and raises RecursionError: maximum recursion depth exceeded, unwinding every one of those ~1000 frames as the exception propagates.

Result: RecursionError ✗ — the missing base case, not the logic, is the whole bug

Python Code

Correct vs broken, side by side

PythonCorrect — base case stops the recursion
def factorial(n):
    if n == 1:            # base case — this is what makes it terminate
        return 1
    return n * factorial(n - 1)

print(factorial(5))   # 120
PythonBroken — no base case, recurses forever
def broken_factorial(n):
    # No base case at all! n just keeps decreasing past 1, forever.
    return n * broken_factorial(n - 1)

print(broken_factorial(5))
# RecursionError: maximum recursion depth exceeded

Complexity Notes

Correct versionO(n) callsTerminates in exactly n steps, reaching the base case reliably
Broken version~1000 calls, then crashRuns until Python's recursion limit stops it — not a graceful exit
Two requirementsReachable + movingThe base case must exist, AND every recursive call must move strictly closer to it
Silent until it isn'tNo early warningA broken base case behaves identically to a correct one for many calls — the bug only appears near the recursion limit

Frequently asked questions

Why doesn't Python detect infinite recursion immediately?

Python has no way to know in advance whether a given recursive call will eventually terminate — that's undecidable in general. All it can do is count how many frames are currently active and raise an error once that count crosses a configured limit (about 1000 by default), which is why the crash happens after hundreds of calls, not on the first one.

Is having a base case enough by itself?

No — the base case also has to be reachable. A function like factorial(n) with the correct check if n == 1: return 1 but a recursive call of factorial(n) (forgetting the - 1) has a perfectly valid base case that can never actually be reached, because n never changes. It fails exactly the same way — RecursionError — for a different reason.

Can I just raise Python's recursion limit to avoid this error?

sys.setrecursionlimit() raises the ceiling, but it doesn't fix a genuinely broken base case — it just delays the crash and risks a real stack overflow at the OS level, which can crash the interpreter instead of raising a catchable Python exception. Fix the base case; don't raise the limit to work around it.

How do I check my own recursive function has a correct base case?

Ask two questions: does at least one condition return without recursing (the base case exists), and does every recursive call use an input that's provably closer to that condition (the recursive case makes progress)? If both are true for every possible valid input, the function is guaranteed to terminate.

Watch the stack climb toward the limit — and crash

Paste your own recursive function into LearnBug and see exactly where a missing base case fails.

Open Playground →