---
# === IDENTITY ===
id: software/debugging/python-recursion-stackoverflow/2026
canonical_question: "How do I fix Python RecursionError and stack overflow?"
aliases:
  - "Python RecursionError maximum recursion depth exceeded"
  - "Python stack overflow recursion"
  - "sys.setrecursionlimit Python"
  - "Python infinite recursion fix"
  - "Python recursion limit increase"
  - "Convert recursive function to iterative Python"
  - "Python RecursionError base case missing"
  - "Python maximum recursion depth exceeded in comparison"
  - "Python trampoline pattern recursion"
  - "Python tail recursion optimization"
entity_type: software_reference
domain: software > debugging > python_recursion_stackoverflow
region: global
jurisdiction: global
temporal_scope: 2015-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.94
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Python 3.12 (2023-10) — sys.setrecursionlimit no longer controls C stack recursion limit (Py_C_RECURSION_LIMIT is hardcoded)"
  next_review: 2026-08-22
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "Default recursion limit is 1000 on CPython — exceeding it raises RecursionError (RuntimeError before Python 3.5)"
  - "sys.setrecursionlimit() only controls Python-level recursion since 3.12 — C extension recursion is governed by the hardcoded Py_C_RECURSION_LIMIT and cannot be changed at runtime"
  - "Setting sys.setrecursionlimit too high can cause a segmentation fault (C stack overflow) — the safe maximum depends on OS thread stack size (typically 1-8 MB)"
  - "CPython does NOT optimize tail recursion — tail-recursive functions consume the same stack depth as any other recursive function"
  - "The resource module (for adjusting OS stack size) is only available on Unix/Linux/macOS — not on Windows"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Error is MemoryError rather than RecursionError"
    use_instead: "software/debugging/python-memory-leaks/2026"
  - condition: "Segmentation fault without a Python traceback (C extension crash)"
    use_instead: "software/debugging/c-cpp-segfault/2026"
  - condition: "Error is 'maximum recursion depth exceeded in comparison' during __eq__ or __hash__"
    use_instead: "Fix __eq__/__hash__ to avoid recursive comparison — see Anti-Pattern #4 in this unit"

# === AGENT HINTS ===
inputs_needed:
  - key: recursion_type
    question: "What kind of recursive code is causing the error?"
    type: choice
    options: ["Tree/graph traversal (DFS/BFS)", "Mathematical computation (factorial/fibonacci)", "Data structure processing (nested JSON/XML)", "Parser/compiler (recursive descent)", "Accidental recursion (__repr__/__str__/__eq__)"]
  - key: depth_needed
    question: "How deep does the recursion need to go?"
    type: choice
    options: ["Under 1000 (fix base case)", "1000-10000 (increase limit or convert to iterative)", "Over 10000 (must convert to iterative)"]
  - key: python_version
    question: "Which Python version are you running?"
    type: choice
    options: ["3.8-3.11", "3.12+", "3.13t/3.14t (free-threaded)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/python-recursion-stackoverflow/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/python-memory-leaks/2026"
      label: "Python Memory Leaks"
    - id: "software/debugging/python-importerror/2026"
      label: "Python ImportError/ModuleNotFoundError"
  often_confused_with:
    - id: "software/debugging/c-cpp-segfault/2026"
      label: "C/C++ Segmentation Faults (C stack overflow, not Python RecursionError)"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Python — sys: System-specific parameters and functions (setrecursionlimit, getrecursionlimit)"
    author: Python Software Foundation
    url: https://docs.python.org/3/library/sys.html
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src2
    title: "Python — Built-in Exceptions (RecursionError)"
    author: Python Software Foundation
    url: https://docs.python.org/3/library/exceptions.html
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src3
    title: "PEP 651 — Robust Stack Overflow Handling"
    author: Mark Shannon
    url: https://peps.python.org/pep-0651/
    type: rfc_spec
    published: 2021-01-12
    reliability: authoritative
  - id: src4
    title: "Real Python — Thinking Recursively in Python"
    author: Real Python
    url: https://realpython.com/python-thinking-recursively/
    type: technical_blog
    published: 2024-06-01
    reliability: high
  - id: src5
    title: "Python — functools: lru_cache"
    author: Python Software Foundation
    url: https://docs.python.org/3/library/functools.html
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src6
    title: "Recursion to Iteration: Converting Recursive Functions to Iterative"
    author: Baeldung
    url: https://www.baeldung.com/cs/convert-recursion-to-iteration
    type: technical_blog
    published: 2024-01-01
    reliability: high
  - id: src7
    title: "sys.setrecursionlimit docs are incorrect in 3.12 and 3.13 (CPython Issue #112282)"
    author: CPython Contributors
    url: https://github.com/python/cpython/issues/112282
    type: community_resource
    published: 2023-11-20
    reliability: high
---

# How to Fix Python RecursionError and Stack Overflow

## TL;DR

- **Bottom line**: Python's default recursion limit is 1000 calls. `RecursionError: maximum recursion depth exceeded` means your code either (1) has no base case, (2) has an unreachable base case, or (3) legitimately needs deeper recursion. Fix the base case first; convert to iterative with an explicit stack for deep recursion; use `sys.setrecursionlimit()` only as a temporary measure.
- **Key tool/command**: `sys.setrecursionlimit(N)` to raise the limit temporarily; convert to iterative with `stack = [initial]; while stack: item = stack.pop()` for a permanent fix.
- **Watch out for**: Blindly raising the limit with `sys.setrecursionlimit(10**6)` without increasing the OS thread stack size — this causes a segfault instead of a clean Python exception.
- **Works with**: All CPython versions 2.7+/3.x. Behavior changed in 3.12+ (C stack limit is now separate and hardcoded). PyPy has a different default limit (~1000 but measured differently).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- **Default limit is 1000**: CPython sets `sys.getrecursionlimit()` to 1000 by default. This includes all nested Python function calls, not just the recursive function itself. Framework overhead (Django, Flask) may consume 50-100 frames before your code runs. [src1]
- **Python 3.12+ split the limit**: `sys.setrecursionlimit()` only controls Python-level recursion. C-level recursion (built-in functions, C extensions) is governed by `Py_C_RECURSION_LIMIT`, which is hardcoded and cannot be changed at runtime. Code mixing Python and C recursion may hit the C limit despite raising the Python limit. [src7]
- **No tail-call optimization in CPython**: Tail-recursive functions in Python are NOT optimized by the interpreter. Every recursive call adds a new frame to the call stack regardless of position. Do not rely on tail recursion for deep recursion. [src4]
- **Segfault risk above OS stack size**: Each Python frame uses approximately 1-2 KB of C stack space. With a typical 8 MB thread stack, the practical maximum is roughly 4000-8000 frames. Setting `sys.setrecursionlimit(100000)` without increasing OS stack size will cause a segmentation fault, not a RecursionError. [src1, src3]
- **resource module is Unix-only**: `resource.setrlimit(resource.RLIMIT_STACK, ...)` to increase OS stack size is not available on Windows. On Windows, thread stack size can only be set via `threading.stack_size()` for new threads. [src1]

## Quick Reference

| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Missing base case | ~30% of cases | `RecursionError` immediately or after small input | Add `if` check that returns without recursing [src4] |
| 2 | Unreachable base case (off-by-one, wrong condition) | ~20% of cases | Works for small inputs, fails at larger ones | Debug base case condition; print args at each call [src4] |
| 3 | Accidental recursion (`__repr__`, `__str__`, `__eq__`) | ~15% of cases | `RecursionError` in `repr()` or comparison | Avoid self-referencing in dunder methods [src2] |
| 4 | Legitimate deep recursion (tree/graph DFS) | ~12% of cases | Fails on deep/large inputs only | Convert to iterative with explicit stack [src6] |
| 5 | Infinite mutual recursion (A calls B calls A) | ~8% of cases | Traceback alternates between two functions | Break cycle; introduce base case in one function [src4] |
| 6 | Property getter calls itself | ~5% of cases | `RecursionError` on attribute access | Use `self._attr` (backing attribute), not `self.attr` in getter [src2] |
| 7 | `@lru_cache` with deep first call | ~3% of cases | First call to `fib(5000)` fails, subsequent work | Build up cache iteratively: `for i in range(5000): fib(i)` [src5] |
| 8 | JSON/XML parsing of deeply nested data | ~3% of cases | Fails on specific input files | Use iterative parser or `sys.setrecursionlimit()` for known depth [src6] |
| 9 | Metaclass or decorator recursion | ~2% of cases | `RecursionError` during class definition or decoration | Check decorator/metaclass for circular calls [src2] |
| 10 | `pickle`/`copy.deepcopy` on recursive structures | ~2% of cases | `RecursionError` during serialization | Implement `__reduce__` or `__deepcopy__`; flatten structure [src1] |

## Decision Tree

```
START
├── Does the traceback show the SAME function repeating?
│   ├── YES — Is there a base case (if/return without recursing)?
│   │   ├── NO → Add a base case. See Step 1.
│   │   └── YES → Is the base case reachable? Add print(args) to check.
│   │       ├── NO → Fix the condition. See Step 2.
│   │       └── YES → Recursion is legitimately deep ↓
│   └── NO — Do TWO functions alternate in the traceback?
│       ├── YES → Mutual recursion. Break the cycle. See Step 3.
│       └── NO → Check for __repr__/__str__/__eq__ in traceback ↓
├── Does traceback mention __repr__, __str__, __eq__, or property?
│   ├── YES → Accidental self-reference in dunder method. See Anti-Pattern #4.
│   └── NO ↓
├── Is recursion depth < 1000 but still failing?
│   ├── YES → Framework overhead consuming frames. Increase limit by 500-1000.
│   └── NO ↓
├── Do you need depth > 10000?
│   ├── YES → MUST convert to iterative with explicit stack. See Step 5.
│   └── NO ↓
├── Do you need depth 1000-10000?
│   ├── YES → Option A: sys.setrecursionlimit(N+100). Option B: Convert to iterative.
│   │   └── If setting limit: also increase OS stack size or run in a new thread.
│   └── NO ↓
└── DEFAULT → Check for accidental recursion (property calling itself, __init__ calling __init__).
```

## Step-by-Step Guide

### 1. Identify the missing or broken base case

Most RecursionErrors are caused by a missing or unreachable base case. Read the traceback to find the repeating function call and verify its termination condition. [src4]

```python
# Example: broken base case — never reaches 0 for negative input
def factorial(n):
    if n == 0:       # Bug: what if n < 0?
        return 1
    return n * factorial(n - 1)

# factorial(-1) → RecursionError

# FIXED: handle edge cases
def factorial(n):
    if n < 0:
        raise ValueError(f"factorial not defined for negative: {n}")
    if n <= 1:       # Covers 0 and 1
        return 1
    return n * factorial(n - 1)
```

**Verify**: `factorial(0)` returns `1`, `factorial(-1)` raises `ValueError`, `factorial(10)` returns `3628800`.

### 2. Debug the base case with argument tracing

When the base case exists but is never reached, log the arguments at each recursive call. [src4]

```python
import sys

def find_path(graph, start, end, path=None):
    if path is None:
        path = []
    path = path + [start]
    if start == end:
        return path
    for node in graph.get(start, []):
        if node not in path:  # Prevents infinite loops in cyclic graphs
            result = find_path(graph, node, end, path)
            if result:
                return result
    return None

# Debug: temporarily add tracing
def find_path_debug(graph, start, end, path=None, depth=0):
    print(f"{'  ' * depth}find_path(start={start}, end={end}, path={path})")
    if depth > 20:
        raise RuntimeError("Debug: stopping at depth 20 to inspect")
    # ... rest of function
```

**Verify**: Tracing output shows why the base case is not reached — common causes: wrong comparison operator, mutating the input, or missing cycle detection.

### 3. Break mutual recursion cycles

When function A calls B and B calls A without termination. [src4]

```python
# BROKEN: mutual recursion with no base case
def is_even(n):
    if n == 0:
        return True
    return is_odd(n - 1)

def is_odd(n):
    if n == 0:
        return False
    return is_even(n - 1)

# is_even(-1) → RecursionError (n decreases past 0)

# FIXED: guard against negative input
def is_even(n):
    n = abs(n)
    if n == 0:
        return True
    return is_odd(n - 1)

def is_odd(n):
    n = abs(n)
    if n == 0:
        return False
    return is_even(n - 1)
```

**Verify**: `is_even(-4)` returns `True`, `is_odd(3)` returns `True`.

### 4. Apply memoization to reduce effective depth

For problems like Fibonacci where the same subproblems recur, `@lru_cache` reduces actual recursion depth dramatically. [src5]

```python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

# Problem: fib(1500) still hits recursion limit on first cold call
# Solution: warm up the cache incrementally
def fib_safe(n):
    """Compute fib(n) without hitting recursion limit."""
    # Build cache in chunks of 500 to stay under the limit
    chunk = 500
    for target in range(chunk, n + 1, chunk):
        fib(target)
    return fib(n)

print(fib_safe(10000))  # Works — cache built incrementally
```

**Verify**: `fib_safe(10000)` completes without RecursionError.

### 5. Convert to iterative with an explicit stack

The universal fix for deep recursion. Replace the call stack with a list. [src6]

```python
# RECURSIVE — fails for trees deeper than ~1000
def dfs_recursive(graph, start):
    visited = set()
    def _dfs(node):
        visited.add(node)
        for neighbor in graph.get(node, []):
            if neighbor not in visited:
                _dfs(neighbor)
    _dfs(start)
    return visited

# ITERATIVE — handles any depth, uses O(n) heap memory
def dfs_iterative(graph, start):
    visited = set()
    stack = [start]
    while stack:
        node = stack.pop()
        if node in visited:
            continue
        visited.add(node)
        # Add neighbors in reverse for same traversal order as recursive
        for neighbor in reversed(graph.get(node, [])):
            if neighbor not in visited:
                stack.append(neighbor)
    return visited
```

**Verify**: Both produce identical `visited` sets. Iterative version handles graphs with 100K+ nodes.

### 6. Raise the limit safely (temporary measure)

When you cannot refactor immediately and know the required depth. [src1]

```python
import sys
import threading

# Check current limit
print(f"Current limit: {sys.getrecursionlimit()}")  # 1000

# Option A: Raise limit (risk of segfault if too high)
sys.setrecursionlimit(5000)

# Option B: Run deep recursion in a thread with larger stack
def run_deep_recursion():
    sys.setrecursionlimit(50000)
    result = deep_recursive_function(30000)
    return result

# Set thread stack size to 64 MB (default is often 8 MB)
threading.stack_size(67108864)  # 64 * 1024 * 1024
thread = threading.Thread(target=run_deep_recursion)
thread.start()
thread.join()
```

**Verify**: `sys.getrecursionlimit()` returns the new value. On Unix: `import resource; print(resource.getrlimit(resource.RLIMIT_STACK))` shows OS stack size.

### 7. Use the trampoline pattern for tail-recursive functions

When you have a naturally tail-recursive function and want to avoid stack growth without a full rewrite. [src4, src6]

```python
# Trampoline decorator — converts tail recursion to iteration
def trampoline(fn):
    def wrapper(*args, **kwargs):
        result = fn(*args, **kwargs)
        while callable(result):
            result = result()
        return result
    return wrapper

# Tail-recursive factorial using trampoline
@trampoline
def factorial_tr(n, acc=1):
    if n <= 1:
        return acc
    return lambda: factorial_tr(n - 1, n * acc)  # Return thunk, not recursive call

print(factorial_tr(50000))  # Works — constant stack depth
```

**Verify**: `factorial_tr(50000)` completes without RecursionError. Stack depth stays at 2 frames.

## Code Examples

### Python: Iterative tree traversal (in-order, pre-order, post-order)

```python
# Input:  Binary tree root node
# Output: List of values in specified traversal order

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def inorder_iterative(root):
    """In-order traversal without recursion."""
    result, stack = [], []
    current = root
    while current or stack:
        while current:
            stack.append(current)
            current = current.left
        current = stack.pop()
        result.append(current.val)
        current = current.right
    return result
```

### Python: Iterative flood fill (replaces recursive DFS)

```python
# Input:  2D grid, start position, new color
# Output: Grid with connected region recolored

def flood_fill(image, sr, sc, new_color):
    """Iterative flood fill — safe for any grid size."""
    rows, cols = len(image), len(image[0])
    old_color = image[sr][sc]
    if old_color == new_color:
        return image
    stack = [(sr, sc)]
    while stack:
        r, c = stack.pop()
        if (0 <= r < rows and 0 <= c < cols
                and image[r][c] == old_color):
            image[r][c] = new_color
            stack.extend([(r+1, c), (r-1, c),
                          (r, c+1), (r, c-1)])
    return image
```

### Python: Recursive-to-iterative with state stack (general pattern)

```python
# Input:  Any recursive function
# Output: Equivalent iterative function using explicit stack

# PATTERN: Replace call stack with explicit frames
# Each "frame" stores the state needed when returning

def flatten_nested(data):
    """Flatten arbitrarily nested lists — iterative."""
    result = []
    stack = [iter(data)]       # Stack of iterators
    while stack:
        try:
            item = next(stack[-1])
            if isinstance(item, list):
                stack.append(iter(item))  # "Recurse"
            else:
                result.append(item)
        except StopIteration:
            stack.pop()                   # "Return"
    return result

# flatten_nested([1, [2, [3, 4], 5], 6]) → [1, 2, 3, 4, 5, 6]
```

## Anti-Patterns

### Wrong: Blindly raising the recursion limit to a huge number

```python
# BAD — causes segfault instead of clean RecursionError [src1, src3]
import sys
sys.setrecursionlimit(10**6)

def deep(n):
    if n == 0:
        return 0
    return deep(n - 1) + 1

deep(500000)  # Segmentation fault (core dumped)
```

### Correct: Convert to iterative or raise limit with matching stack size

```python
# GOOD — iterative version with no stack limit [src6]
def deep(n):
    result = 0
    for _ in range(n):
        result += 1
    return result

deep(500000)  # Works instantly, no stack concern
```

### Wrong: Using recursion for linear iteration

```python
# BAD — recursion where a simple loop suffices [src4]
def sum_list(lst, index=0):
    if index >= len(lst):
        return 0
    return lst[index] + sum_list(lst, index + 1)

sum_list(range(2000))  # RecursionError
```

### Correct: Use a loop for linear iteration

```python
# GOOD — natural loop, no recursion overhead [src4]
def sum_list(lst):
    return sum(lst)  # Built-in, C-optimized

# Or explicitly:
def sum_list(lst):
    total = 0
    for item in lst:
        total += item
    return total
```

### Wrong: Property getter that references itself

```python
# BAD — property.getter calls self.name which triggers the getter again [src2]
class User:
    def __init__(self, name):
        self.name = name  # Triggers the setter

    @property
    def name(self):
        return self.name  # Calls the getter recursively!

    @name.setter
    def name(self, value):
        self.name = value  # Calls the setter recursively!

User("Alice")  # RecursionError
```

### Correct: Use a private backing attribute

```python
# GOOD — separate backing attribute breaks the recursion [src2]
class User:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return self._name  # Accesses backing attribute

    @name.setter
    def name(self, value):
        self._name = value  # Sets backing attribute
```

### Wrong: Recursive __repr__ on objects that reference each other

```python
# BAD — circular reference causes infinite repr recursion [src2]
class Node:
    def __init__(self, val, neighbors=None):
        self.val = val
        self.neighbors = neighbors or []

    def __repr__(self):
        return f"Node({self.val}, neighbors={self.neighbors})"

a, b = Node(1), Node(2)
a.neighbors.append(b)
b.neighbors.append(a)
print(a)  # RecursionError: Node.__repr__ → Node.__repr__ → ...
```

### Correct: Limit repr depth or show only IDs

```python
# GOOD — repr does not recurse into neighbors [src2]
class Node:
    def __init__(self, val, neighbors=None):
        self.val = val
        self.neighbors = neighbors or []

    def __repr__(self):
        neighbor_ids = [n.val for n in self.neighbors]
        return f"Node({self.val}, neighbors={neighbor_ids})"
```

## Common Pitfalls

- **Framework frames eat into the limit**: Django, Flask, and FastAPI add 30-80 frames before your handler runs. If your recursion needs exactly 1000 depth, it will fail. Set the limit 200+ above your actual need, or better yet, avoid deep recursion in request handlers. [src1]
- **`@lru_cache` does not prevent deep recursion**: Memoization reduces *redundant* calls but the first cold call to `fib(2000)` still recurses 2000 deep. Warm up the cache incrementally or use bottom-up dynamic programming. [src5]
- **`sys.setrecursionlimit` affects all threads**: The limit is process-wide. Raising it for one computation affects every thread's safety margin. Prefer running deep recursion in a dedicated thread with `threading.stack_size()` set appropriately. [src1]
- **Catching RecursionError and continuing is unreliable**: After RecursionError is raised, you have very little stack space remaining. Any exception handler that does non-trivial work (logging, cleanup) may itself trigger another RecursionError. Keep handlers minimal. [src2]
- **`sys.setrecursionlimit` changed in Python 3.12**: Since 3.12, the limit only controls Python frames. C-level recursion (built-in functions, json module, pickle, re) has a separate hardcoded limit. Raising the Python limit does not help if the C limit is hit. [src7]
- **Generators are NOT recursive**: Using `yield from` in a generator does NOT add to the recursion depth. But `yield from recursive_generator()` does create a chain of generator frames that *does* count. Use iterative generators for deep data. [src4]
- **PyPy has different limits**: PyPy's default recursion limit is also ~1000 but PyPy frames are lighter. The same `sys.setrecursionlimit` value allows deeper effective recursion on PyPy than CPython. Do not assume identical behavior. [src1]
- **Multiprocessing forks reset the limit**: If you use `multiprocessing.Process`, child processes start with the default limit (1000) unless you explicitly set it again in the child. [src1]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (48 lines)

```bash
# Check current recursion limit
python -c "import sys; print(sys.getrecursionlimit())"
# Check OS thread stack size (Linux/macOS)
ulimit -s
# Check Python frame size estimate (bytes per frame)
# ... (see full script)
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Python 3.14 (Oct 2025) | Current | PEP 651 (Robust Stack Overflow Handling) may land — separate `StackOverflow` and `RecursionOverflow` exceptions | Watch for new exception types if PEP 651 is accepted [src3] |
| Python 3.13 (Oct 2024) | Supported | `Py_C_RECURSION_LIMIT` hardcoded; free-threaded build available | C recursion limit cannot be changed at runtime [src7] |
| Python 3.12 (Oct 2023) | Supported | `sys.setrecursionlimit` only controls Python-level recursion | Code relying on limit for C extension recursion may break [src7] |
| Python 3.11 (Oct 2022) | Security fixes | Specializing adaptive interpreter; exception groups | No recursion-specific changes |
| Python 3.8 (Oct 2019) | EOL (Oct 2024) | — | Last version before major internal changes |
| Python 3.5 (Sep 2015) | EOL | `RecursionError` introduced (was `RuntimeError`) | Change `except RuntimeError` to `except RecursionError` for specificity [src2] |
| Python 2.7 (Jul 2010) | EOL (Jan 2020) | Raises `RuntimeError`, no `RecursionError` class | Upgrade to Python 3.x |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Base case is missing or wrong | You need depth > 10,000 | Convert to iterative with explicit stack |
| Recursion depth is slightly over 1000 | You're writing a performance-critical hot loop | Iterative version is faster (no function call overhead) |
| Debugging mutual recursion between functions | Input size is unbounded (user-controlled depth) | Iterative — recursion depth becomes a DoS vector |
| Prototyping and depth is known/bounded | Working with very deeply nested JSON/XML | Use `json.loads` limit parameter (3.12+) or iterative parser |
| `@lru_cache` can reduce effective depth | Deploying to production with unknown inputs | Iterative is always safer for production code |

## Important Caveats

- On Python 3.12+, `sys.setrecursionlimit()` and the C stack recursion limit are decoupled. Raising the Python limit does not prevent C-level stack overflow. This affects code that mixes Python and C extension calls in the recursion chain (e.g., `json.loads` on deeply nested JSON). [src7]
- Setting a very high recursion limit (e.g., 10^6) without increasing the OS thread stack size will cause a segmentation fault — an unrecoverable crash with no Python traceback. The OS stack size is typically 1-8 MB and each Python frame uses ~1-2 KB of C stack. [src1, src3]
- `RecursionError` was introduced in Python 3.5. Code that catches `RuntimeError` to handle recursion may inadvertently catch unrelated runtime errors. Use `except RecursionError` specifically. [src2]
- The trampoline pattern (returning thunks instead of making recursive calls) eliminates stack growth but adds overhead per call from the wrapper loop. For performance-critical code, a direct iterative rewrite is faster. [src6]
- Python's `re` module uses C-level recursion for regex matching. Very complex patterns on long strings can hit the C recursion limit independently of `sys.setrecursionlimit`. Use `re.compile` with simpler patterns or the `regex` library (which supports iterative matching). [src1]
- In `asyncio`, recursive `async` functions consume coroutine frames that count toward the recursion limit. The same 1000-depth limit applies. Convert to iterative with an explicit task queue or `asyncio.Queue`. [src4]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Python Memory Leaks](/software/debugging/python-memory-leaks/2026)
- [Python ImportError/ModuleNotFoundError](/software/debugging/python-importerror/2026)
- [C/C++ Segmentation Faults](/software/debugging/c-cpp-segfault/2026)
