Basic Recursion
Understand the fundamental technique of solving problems by having functions call themselves. Master base cases, recursive cases, and the call stack to build the foundation for trees, graphs, and dynamic programming.
Learn & Reference
Understanding the Pattern
Basic Recursion
Recursion is when a function calls itself to solve a smaller version of the same problem. It's not just a technique — it's a way of thinking. Many data structures (trees, graphs, nested lists) are inherently recursive, making recursion the natural approach.
The Two Essential Parts
Base Case
The condition where the recursion stops. Without it, you get infinite recursion and a stack overflow. The base case handles the simplest possible input directly, without making a recursive call.
Recursive Case
The part where the function calls itself with a "smaller" input — closer to the base case. Each recursive call must make progress toward the base case, or the recursion will never terminate.
How It Works: The Call Stack
Each recursive call creates a new frame on the call stack with its own local variables. When a base case is reached, the stack starts "unwinding" — each frame returns its result to the caller above it.
Common Patterns
Accumulate
Build up a result as you recurse: sums, products, concatenated strings. Pass the accumulated value as a parameter or return it.
Divide and Conquer
Split the problem in half, solve each half recursively, then combine. This is how merge sort and binary search work.
Build Up
Start from the base case and construct the solution on the way back up the call stack. Each level adds one piece to the result.
Generate All Possibilities
Recursion naturally explores all branches of a decision tree. This is the basis for permutations, combinations, and backtracking.
Recursion vs Iteration
Every recursive solution can be rewritten iteratively (and vice versa). Recursion is clearer for tree/graph traversal and divide-and-conquer. Iteration is usually more memory-efficient since it avoids call stack overhead.
Common Mistakes
- Missing base case: Without a stopping condition, recursion runs forever (stack overflow)
- Base case doesn't cover all terminal states: Make sure edge cases like empty input, zero, and negative values are handled
- Not making progress: Each recursive call must move closer to the base case.
f(n)callingf(n)is an infinite loop - Stack overflow on large inputs: Deep recursion (10,000+ levels) can exceed the call stack. Consider iteration or tail-call optimization
- Redundant computation: Naive recursion (like fibonacci) recalculates the same values. Use memoization or convert to iteration
When to Use
Template
1# Basic Recursion2# 1. Define the base case (when to stop)3# 2. Define the recursive case (call self with smaller input)45def solve(n):6if n <= 0: # base case7return 08return n + solve(n - 1) # recursive case
Syntax Reference
1# === Basic recursive function ===2def factorial(n):3if n <= 1: # base case4return 15return n * factorial(n - 1) # recursive case67# === Multiple base cases ===8def fibonacci(n):9if n <= 0: return 010if n == 1: return 111return fibonacci(n - 1) + fibonacci(n - 2)1213# === Recursion with accumulator ===14def sum_list(arr, index=0):15if index == len(arr):16return 017return arr[index] + sum_list(arr, index + 1)1819# === Recursion limit ===20import sys21sys.getrecursionlimit() # default ~100022sys.setrecursionlimit(10000) # increase if needed2324# === Memoization ===25from functools import lru_cache26@lru_cache(maxsize=None)27def fib(n):28if n <= 1: return n29return fib(n-1) + fib(n-2)
Common Recipes
1# --- Recursive Sum ---2def recursive_sum(arr):3if not arr:4return 05return arr[0] + recursive_sum(arr[1:])67# --- Recursive Reverse ---8def reverse_string(s):9if len(s) <= 1:10return s11return reverse_string(s[1:]) + s[0]1213# --- Power Function ---14def power(base, exp):15if exp == 0:16return 117if exp % 2 == 0:18half = power(base, exp // 2)19return half * half20return base * power(base, exp - 1)2122# --- Flatten Nested List ---23def flatten(arr):24result = []25for item in arr:26if isinstance(item, list):27result.extend(flatten(item))28else:29result.append(item)30return result
Complexity
| Pattern | Time | Space (Stack) | Notes |
|---|---|---|---|
| Linear recursion (n calls) | O(n) | O(n) | e.g., factorial, sum |
| Binary recursion (2 calls/level) | O(2^n) | O(n) | e.g., naive fibonacci |
| Binary recursion + memo | O(n) | O(n) | Each subproblem computed once |
| Divide and conquer | O(n log n) | O(log n) | e.g., merge sort |
| Generate permutations | O(n!) | O(n) | n! leaves in the recursion tree |
| Binary search (recursive) | O(log n) | O(log n) | Halves input each call |
Watch Out
- ✗Missing base case: Without a stopping condition, recursion runs forever (stack overflow)
- ✗Base case doesn't cover all terminal states: Make sure edge cases like empty input, zero, and negative values are handled
- ✗Not making progress: Each recursive call must move closer to the base case. `f(n)` calling `f(n)` is an infinite loop
- ✗Stack overflow on large inputs: Deep recursion (10,000+ levels) can exceed the call stack. Consider iteration or tail-call optimization
- ✗Redundant computation: Naive recursion (like fibonacci) recalculates the same values. Use memoization or convert to iteration