🎯 What is Recursion?
Definition
Recursion occurs when a method calls itself. It's a powerful programming technique where a problem is solved by breaking it down into smaller instances of the same problem.
Content of a Recursive Method
1. Base Case(s) 🛑
- Values of input variables for which we perform no recursive calls
- There should be at least one base case
- Every possible chain of recursive calls must eventually reach a base case
- When we stop the loop - the method must stop calling itself
2. Recursive Calls 🔄
- Calls to the current method
- Each recursive call should be defined so that it makes progress towards a base case
- The method must update parameters to eventually reach the base case
⚠️ Critical Requirements
- Must have a base case (or the recursion never stops!)
- Must make progress toward the base case
- Must call itself (that's what makes it recursive)
🔢 The Recursion Pattern: Factorial
Classic Example: Factorial Function
Mathematical Definition: n! = 1 · 2 · 3 · ... · (n-1) · n
Recursive Definition:
f(n) = { 1 if n = 0
n · f(n-1) else
Java Implementation
Execution Trace: factorial(4)
factorial(4)
↓ call → return 4*6 = 24 (final answer)
factorial(3)
↓ call → return 3*2 = 6
factorial(2)
↓ call → return 2*1 = 2
factorial(1)
↓ call → return 1*1 = 1
factorial(0)
→ return 1 (base case)
How it Works:
- Forward calls: Methods keep calling themselves with decreasing n
- Base case reached: When n = 0, return 1
- Backward returns: Each call returns its result to the caller
- Final result: 4! = 24
Runtime: O(n) - Makes n+1 calls
📝 Common Recursion Examples
1. Binary Search
Problem: Search for an integer in an ordered array
Strategy: Check middle element, then recur on appropriate half
Runtime: O(log n)
Each recursive call divides the search region in half, so there can be at most log n levels.
2. Linear Sum (Array Summation)
Problem: Add all numbers in an integer array
Strategy: Sum first n-1 elements, then add the nth element
Trace: linearSum(data, 5) for data = [4, 3, 6, 2, 8]
linearSum(data, 5) → return 15 + data[4] = 15 + 8 = 23
↓
linearSum(data, 4) → return 13 + data[3] = 13 + 2 = 15
↓
linearSum(data, 3) → return 7 + data[2] = 7 + 6 = 13
↓
linearSum(data, 2) → return 4 + data[1] = 4 + 3 = 7
↓
linearSum(data, 1) → return 0 + data[0] = 0 + 4 = 4
↓
linearSum(data, 0) → return 0 (base case)
3. Reversing an Array
Runtime: O(n)
Each recursive call swaps two elements and moves indices closer to the middle.
4. Computing Powers
❌ Slow Approach: O(n)
Makes n recursive calls
✅ Fast Approach: O(log n)
Uses repeated squaring!
How Recursive Squaring Works:
- 2⁴ = (2²)² = 4² = 16
- 2⁵ = 2 · (2²)² = 2 · 16 = 32
- 2⁸ = (2⁴)² = 16² = 256
Key Insight: Halve the exponent each time → logarithmic complexity!
5. Fibonacci Numbers
Definition:
- F₀ = 0
- F₁ = 1
- Fᵢ = Fᵢ₋₁ + Fᵢ₋₂ for i > 1
❌ Binary Recursion (SLOW)
Runtime: O(2ⁿ) - EXPONENTIAL!
Makes redundant calculations
✅ Linear Recursion (FAST)
Runtime: O(n) - LINEAR!
Returns pair (Fₖ, Fₖ₋₁)
6. English Ruler (Drawing Ticks)
Problem: Draw tick marks on a ruler with varying lengths
Pattern: For a tick of length L, draw smaller ticks (L-1) on both sides
Note: This is an example of binary recursion (two recursive calls per invocation)
🎭 Types of Recursion
1. Linear Recursion
Definition: Makes at most one recursive call per invocation
Characteristics:
- Test for base cases first
- Perform a single recursive call (may have conditionals to decide which call)
- Each recursive call makes progress toward base case
Examples:
- Factorial
- Linear sum
- Binary search
- Array reversal
2. Tail Recursion
Definition: A special case of linear recursion where the recursive call is the last step of the method
⚡ Special Property:
Tail recursive methods can be easily converted to iterative (loop-based) implementations, which are often more efficient.
Tail Recursive Version
Iterative Equivalent
3. Binary Recursion
Definition: Makes two recursive calls for each non-base case
Examples:
- Binary sum (divide array in half, sum each half)
- English ruler drawing
- Binary Fibonacci (inefficient)
4. Multiple Recursion
Definition: Makes potentially many recursive calls (more than two)
Example: Puzzle Solving
Generate all possible combinations/permutations
Recursion Tree for PuzzleSolve(3, [], {a,b,c})
[]
┌─────────┼─────────┐
[a] [b] [c]
┌──┴──┐ ┌──┴──┐ ┌──┴──┐
[ab][ac] [ba][bc] [ca][cb]
↓ ↓ ↓ ↓ ↓ ↓
abc acb bac bca cab cba
⏱️ Complexity Analysis
Time Complexity Comparison
| Algorithm | Type | Time Complexity | Explanation |
|---|---|---|---|
| Factorial | Linear | O(n) | Makes n recursive calls |
| Binary Search | Linear | O(log n) | Halves search space each time |
| Linear Sum | Linear | O(n) | Processes each element once |
| Array Reversal | Tail | O(n) | Swaps n/2 pairs |
| Power (naive) | Linear | O(n) | Multiplies n times |
| Power (squaring) | Linear | O(log n) | Halves exponent each time |
| Binary Sum | Binary | O(n) | Processes each element in tree |
| Binary Fibonacci | Binary | O(2ⁿ) | Exponential - very inefficient! |
| Linear Fibonacci | Linear | O(n) | Makes n-1 calls |
Understanding Call Counts
Binary Fibonacci Analysis
Let nₖ = number of recursive calls for BinaryFib(k):
- n₀ = 1
- n₁ = 1
- n₂ = n₁ + n₀ + 1 = 3
- n₃ = n₂ + n₁ + 1 = 5
- n₄ = n₃ + n₂ + 1 = 9
- n₈ = 67
Notice: nₖ at least doubles every other time → nₖ > 2^(k/2)
This is exponential growth - very bad!
Key Insights for Efficiency
- Avoid redundant calculations: Binary Fibonacci recalculates same values multiple times
- Use clever math: Recursive squaring reduces O(n) to O(log n)
- Consider iterative alternatives: Tail recursive methods can often be converted to loops
- Store intermediate results: Dynamic programming (memoization) can help
📋 Summary & Study Tips
Key Concepts to Remember
1. Three Requirements for Recursion:
- ✅ Must have a base case
- ✅ Must make progress toward base case
- ✅ Must call itself
2. Types of Recursion:
- Linear: One recursive call per invocation
- Tail: Recursive call is the last operation (easily converted to loops)
- Binary: Two recursive calls per invocation
- Multiple: Many recursive calls (used for enumeration/combinations)
3. When to Use Recursion:
- ✅ Problem can be broken into smaller subproblems of the same type
- ✅ Natural recursive structure (trees, divide-and-conquer)
- ✅ Code is clearer/simpler than iterative version
- ❌ Avoid when efficiency is critical and recursion adds overhead
- ❌ Avoid when iterative solution is equally clear
Common Pitfalls to Avoid
- 🚫 Missing base case: Results in infinite recursion (stack overflow)
- 🚫 Not making progress: Recursive call doesn't move toward base case
- 🚫 Redundant calculations: Like binary Fibonacci - use dynamic programming instead
- 🚫 Too many recursive calls: Can cause stack overflow for large inputs
- 🚫 Modifying wrong parameters: Ensure you update parameters correctly
Study Strategies
To Master Recursion:
- Draw recursion trees: Visualize how calls are made and returned
- Trace execution: Follow the values step by step
- Count recursive calls: Understand time complexity
- Identify base cases: Always start here when analyzing recursive code
- Practice converting: Try converting recursive to iterative and vice versa
- Solve problems: Write your own recursive solutions from scratch
Practice Problems
Try implementing these recursively:
- Calculate the nth Fibonacci number (both ways)
- Find the maximum element in an array
- Check if a string is a palindrome
- Calculate the Greatest Common Divisor (GCD)
- Generate all permutations of a string
- Implement merge sort
- Traverse a binary tree
- Solve the Tower of Hanoi puzzle
Quick Reference Card
| Concept | Key Points |
|---|---|
| Base Case | Simplest case that can be solved directly without recursion |
| Recursive Case | Breaks problem into smaller subproblems and calls itself |
| Progress | Each recursive call must move closer to the base case |
| Stack Space | Each recursive call uses stack memory; deep recursion can overflow |
| Tail Recursion | Last operation is the recursive call; can be optimized to iteration |
| Time Complexity | Count how many times the function is called and work per call |
🎓 Final Tip
"To understand recursion, you must first understand recursion." 😊
Practice is key! Start with simple examples and gradually work your way up to more complex problems. Draw diagrams, trace executions, and don't be afraid to experiment!