Course topics

By WebNest Studio

Python Tutorial

Recursion in Python

A recursive function solves a problem by calling itself on a smaller version of the same problem. Many problems are naturally recursive: factorials, walking nested folders or JSON, tree and graph traversal, divide-and-conquer sorting, generating permutations.

This lesson covers base cases and recursive cases, how the call stack works, Python's recursion limit, memoization with functools.lru_cache, recursion over nested data, and when an iterative solution is better.

Base Case and Recursive Case

Every recursive function needs a base case that returns without recursing, and a recursive case that moves toward it. Missing or unreachable base cases cause infinite recursion, which Python stops with RecursionError.

The Call Stack and the Recursion Limit

Each call gets its own frame on the call stack with its own local variables. Python limits the depth (1000 frames by default, see sys.getrecursionlimit()) and does not optimise tail calls, so very deep recursion should be rewritten as a loop.

Memoization

Naive recursive Fibonacci recomputes the same values exponentially many times. Caching results — memoization — with @functools.lru_cache or @functools.cache turns it into a linear-time algorithm with one line.

Examples

Factorial, sum of digits and power

Python
def factorial(n):
    if n <= 1:            # base case
        return 1
    return n * factorial(n - 1)   # recursive case

def digit_sum(n):
    return n if n < 10 else n % 10 + digit_sum(n // 10)

def power(base, exp):
    if exp == 0:
        return 1
    half = power(base, exp // 2)
    return half * half * (base if exp % 2 else 1)

print(factorial(5), digit_sum(98765), power(2, 30))
Output
120 35 1073741824

Memoized Fibonacci and the recursion limit

Python
import sys
from functools import lru_cache

calls = 0
def fib_slow(n):
    global calls
    calls += 1
    return n if n < 2 else fib_slow(n - 1) + fib_slow(n - 2)

@lru_cache(maxsize=None)
def fib_fast(n):
    return n if n < 2 else fib_fast(n - 1) + fib_fast(n - 2)

print(fib_slow(20), "calls:", calls)
print(fib_fast(90))
print(fib_fast.cache_info().misses, "distinct values computed")
print("limit:", sys.getrecursionlimit())

def countdown(n):
    return countdown(n - 1)          # no base case!
try:
    countdown(5)
except RecursionError as e:
    print("RecursionError:", e)
Output
6765 calls: 21891
2880067194370816120
91 distinct values computed
limit: 1000
RecursionError: maximum recursion depth exceeded

Recursion over nested data and permutations

Python
def flatten(items):
    result = []
    for item in items:
        if isinstance(item, list):
            result.extend(flatten(item))
        else:
            result.append(item)
    return result

def count_keys(data):
    if isinstance(data, dict):
        return len(data) + sum(count_keys(v) for v in data.values())
    if isinstance(data, list):
        return sum(count_keys(v) for v in data)
    return 0

def permutations(s):
    if len(s) <= 1:
        return [s]
    return [ch + p for i, ch in enumerate(s) for p in permutations(s[:i] + s[i + 1:])]

print(flatten([1, [2, [3, [4, 5]]], 6]))
print(count_keys({"a": 1, "b": {"c": 2, "d": [{"e": 3}]}}))
print(permutations("abc"))
Output
[1, 2, 3, 4, 5, 6]
5
['abc', 'acb', 'bac', 'bca', 'cab', 'cba']

Common Mistakes

  • Forgetting the base case or never moving toward it.
  • Using naive recursion for overlapping subproblems without memoization.
  • Recursing thousands of levels deep in Python instead of using a loop.
  • Raising the recursion limit with sys.setrecursionlimit to hide a design problem.

Key Points to Remember

  • Recursion needs a base case and a recursive case that shrinks the problem.
  • Each call has its own stack frame; Python's default depth limit is 1000.
  • @lru_cache / @cache memoize results to avoid repeated work.
  • Recursion suits nested data, trees and divide-and-conquer; loops suit deep linear repetition.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.