Recursion Fundamentals¶
Recursion is a programming technique where a function calls itself to solve a problem by breaking it down into smaller, similar subproblems.
Core Concepts¶
What is Recursion?¶
Recursion occurs when a function calls itself with modified parameters until it reaches a base case. It's a fundamental problem-solving approach that mirrors mathematical induction.
Essential Components¶
Every recursive function must have:
- Base Case: A condition that stops the recursion
- Recursive Case: The function calling itself with modified parameters
- Progress Toward Base Case: Each recursive call must move closer to the base case
Basic Structure¶
def recursive_function(parameters):
# Base case
if base_condition:
return base_value
# Recursive case
return recursive_function(modified_parameters)
Simple Examples¶
Example 1: Factorial¶
def factorial(n):
# Base case
if n <= 1:
return 1
# Recursive case
return n * factorial(n - 1)
# Usage
print(factorial(5)) # Output: 120
How it works: - factorial(5) = 5 * factorial(4) - factorial(4) = 4 * factorial(3) - factorial(3) = 3 * factorial(2) - factorial(2) = 2 * factorial(1) - factorial(1) = 1 (base case)
Example 2:¶
Fibonacci Sequence: Naive approach¶
def fibonacci(n):
# Base cases
if n <= 1:
return n
# Recursive case
return fibonacci(n - 1) + fibonacci(n - 2)
# Usage
print(fibonacci(6)) # Output: 8
Fibonacci Sequence: Efficient Implementation using memoization¶
def fibonacci(n, computed = {0: 0, 1: 1}):
if n not in computed:
computed[n] = fib(n-1, computed) + fib(n-2, computed)
return computed[n]
This computes the n-th Fibonacci number in linear time, O(n)
Example 3: Sum of Array¶
def array_sum(arr, index=0):
# Base case
if index >= len(arr):
return 0
# Recursive case
return arr[index] + array_sum(arr, index + 1)
# Usage
numbers = [1, 2, 3, 4, 5]
print(array_sum(numbers)) # Output: 15
Types of Recursion¶
1. Linear Recursion¶
Function makes one recursive call per execution.
def countdown(n):
if n <= 0:
print("Done!")
return
print(n)
countdown(n - 1)
2. Binary Recursion¶
Function makes two recursive calls per execution.
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
3. Tail Recursion¶
Recursive call is the last operation in the function.
def factorial_tail(n, accumulator=1):
if n <= 1:
return accumulator
return factorial_tail(n - 1, n * accumulator)
4. Multiple Recursion¶
Function makes more than two recursive calls.
def tree_traversal(node):
if node is None:
return
# Process current node
process(node)
# Recursive calls for each child
for child in node.children:
tree_traversal(child)
Recursion vs Mathematical Induction¶
Recursion closely follows the principle of mathematical induction:
- Base Case = Prove for the smallest case
- Inductive Step = If true for k, prove for k+1
- Recursive Step = Assume solution works for smaller problems
Memory and Stack¶
Call Stack¶
Each recursive call adds a new frame to the call stack:
def print_stack_depth(n, depth=0):
print(f"Depth {depth}: n = {n}")
if n <= 0:
return
print_stack_depth(n - 1, depth + 1)
print_stack_depth(3)
Output:
Depth 0: n = 3
Depth 1: n = 2
Depth 2: n = 1
Depth 3: n = 0
Stack Overflow¶
Too many recursive calls can cause stack overflow:
# This will cause stack overflow for large n
def bad_recursion(n):
if n == 0:
return 0
return 1 + bad_recursion(n - 1)
# Python's default recursion limit is around 1000
import sys
print(sys.getrecursionlimit()) # Usually 1000
Best Practices¶
1. Always Define Base Case¶
# Good
def power(base, exp):
if exp == 0:
return 1
return base * power(base, exp - 1)
# Bad - no base case leads to infinite recursion
def bad_power(base, exp):
return base * bad_power(base, exp - 1)
2. Ensure Progress Toward Base Case¶
# Good - exp decreases each call
def power(base, exp):
if exp == 0:
return 1
return base * power(base, exp - 1)
# Bad - exp never changes
def bad_power(base, exp):
if exp == 0:
return 1
return base * bad_power(base, exp) # exp never decreases
3. Consider Iterative Alternatives¶
# Recursive (can cause stack overflow)
def fibonacci_recursive(n):
if n <= 1:
return n
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
# Iterative (more efficient)
def fibonacci_iterative(n):
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
When to Use Recursion¶
Good Use Cases:¶
- Tree and graph traversal
- Divide and conquer algorithms
- Problems with recursive structure (fractals, nested data)
- Backtracking problems
Avoid Recursion When:¶
- Simple iterative solution exists
- Deep recursion expected (stack overflow risk)
- Performance is critical (iterative often faster)
Common Pitfalls¶
1. Missing Base Case¶
# Will run forever
def infinite_recursion(n):
return infinite_recursion(n - 1)
2. Incorrect Base Case¶
# Base case never reached for negative numbers
def factorial(n):
if n == 1: # Should be n <= 1
return 1
return n * factorial(n - 1)
3. No Progress Toward Base Case¶
# n never decreases
def no_progress(n):
if n == 0:
return 0
return no_progress(n) # Should be no_progress(n - 1)
Related Topics¶
- Recursive Algorithms - Common recursive algorithms
- Recursion vs Iteration - When to choose each approach
- Common Recursive Patterns - Problem-solving patterns