Java Recursion - Complete Tutorial
Master Java Recursion: Learn recursive functions, base cases, recursion vs iteration comparison, factorial, Fibonacci, Tower of Hanoi, recursion tree visualization, and best practices.
Self-Calling
Functions calling themselves
Base Case
Stopping condition
Recursion vs Iteration
Comparison & Trade-offs
Recursion Tree
Visual representation
1. Introduction to Recursion
Recursion is a programming technique where a function calls itself directly or indirectly to solve a problem. It's based on the principle of solving a complex problem by breaking it down into smaller, similar subproblems.
Key Components of Recursion
- Base Case: Condition that stops recursion
- Recursive Case: Function calls itself
- Progress: Moves toward base case
- Stack Usage: Uses call stack for state
- Divide & Conquer: Breaks problem into subproblems
- Backtracking: Returns to previous states
When to Use Recursion
- Problems with recursive structure (tree, graph)
- Mathematical sequences (factorial, Fibonacci)
- Divide and conquer algorithms
- Backtracking problems
- Parsing nested structures (JSON, XML)
- File system traversal
The Golden Rule of Recursion
Every recursive function must have: 1. A base case (stopping condition) and 2. A recursive case that moves toward the base case. Without a base case, recursion leads to infinite loops and StackOverflowError.
public class RecursionStructure {
// Basic recursive function structure
public static void recursiveFunction(parameters) {
// 1. Base Case (stopping condition)
if (baseCondition) {
return baseValue;
}
// 2. Recursive Case (function calls itself)
// with modified parameters moving toward base case
return recursiveFunction(modifiedParameters);
}
// Example: Countdown from n to 1
public static void countdown(int n) {
// Base case
if (n <= 0) {
System.out.println("Blastoff!");
return;
}
// Recursive case
System.out.println(n);
countdown(n - 1); // Move toward base case
}
// Example: Sum of numbers from 1 to n
public static int sum(int n) {
// Base case
if (n <= 0) {
return 0;
}
// Recursive case
return n + sum(n - 1);
}
public static void main(String[] args) {
System.out.println("=== Countdown Example ===");
countdown(5);
System.out.println("\n=== Sum Example ===");
System.out.println("Sum of 1 to 5: " + sum(5));
// Visualizing the recursion
System.out.println("\n=== Recursion Visualization for sum(3) ===");
System.out.println("sum(3)");
System.out.println(" = 3 + sum(2)");
System.out.println(" = 3 + (2 + sum(1))");
System.out.println(" = 3 + (2 + (1 + sum(0)))");
System.out.println(" = 3 + (2 + (1 + 0))");
System.out.println(" = 3 + (2 + 1)");
System.out.println(" = 3 + 3");
System.out.println(" = 6");
}
}
Visualizing Recursion: Call Stack for sum(3)
Stack grows downward - Each recursive call creates a new stack frame
2. Recursion vs Iteration: Complete Comparison
Both recursion and iteration (loops) can solve the same problems, but they have different characteristics, advantages, and trade-offs.
public class FactorialIterative {
// Iterative approach
public static int factorialIterative(int n) {
if (n < 0) {
throw new IllegalArgumentException("Factorial undefined for negative numbers");
}
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// Recursive approach
public static int factorialRecursive(int n) {
if (n < 0) {
throw new IllegalArgumentException("Factorial undefined for negative numbers");
}
// Base case
if (n == 0 || n == 1) {
return 1;
}
// Recursive case
return n * factorialRecursive(n - 1);
}
public static void main(String[] args) {
System.out.println("=== Factorial Comparison ===");
int n = 5;
System.out.println("Factorial of " + n + ":");
System.out.println("Iterative: " + factorialIterative(n));
System.out.println("Recursive: " + factorialRecursive(n));
// Performance test
System.out.println("\n=== Performance Test (n=20) ===");
long startTime, endTime;
startTime = System.nanoTime();
int iterResult = factorialIterative(20);
endTime = System.nanoTime();
System.out.println("Iterative time: " + (endTime - startTime) + " ns");
startTime = System.nanoTime();
int recResult = factorialRecursive(20);
endTime = System.nanoTime();
System.out.println("Recursive time: " + (endTime - startTime) + " ns");
// Memory test
System.out.println("\n=== Memory Considerations ===");
System.out.println("Iterative: Constant O(1) memory");
System.out.println("Recursive: O(n) stack memory (risk of StackOverflowError)");
// Try large n to see stack overflow
System.out.println("\n=== Testing with large n (n=10000) ===");
try {
factorialIterative(10000);
System.out.println("Iterative: Works fine");
} catch (Exception e) {
System.out.println("Iterative error: " + e.getMessage());
}
try {
factorialRecursive(10000);
System.out.println("Recursive: Works fine");
} catch (StackOverflowError e) {
System.out.println("Recursive: StackOverflowError!");
}
}
}
public class FibonacciComparison {
// Iterative Fibonacci (efficient)
public static int fibonacciIterative(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
int prev1 = 0;
int prev2 = 1;
int current = 0;
for (int i = 2; i <= n; i++) {
current = prev1 + prev2;
prev1 = prev2;
prev2 = current;
}
return current;
}
// Simple recursive Fibonacci (inefficient - O(2^n))
public static int fibonacciRecursive(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
// Memoized recursive Fibonacci (efficient - O(n))
public static int fibonacciMemoized(int n) {
int[] memo = new int[n + 1];
return fibonacciMemoHelper(n, memo);
}
private static int fibonacciMemoHelper(int n, int[] memo) {
if (n <= 0) return 0;
if (n == 1) return 1;
if (memo[n] != 0) {
return memo[n];
}
memo[n] = fibonacciMemoHelper(n - 1, memo) +
fibonacciMemoHelper(n - 2, memo);
return memo[n];
}
public static void main(String[] args) {
System.out.println("=== Fibonacci Sequence Comparison ===");
int n = 10;
System.out.println("Fibonacci(" + n + "):");
System.out.println("Iterative: " + fibonacciIterative(n));
System.out.println("Simple Recursive: " + fibonacciRecursive(n));
System.out.println("Memoized Recursive: " + fibonacciMemoized(n));
System.out.println("\n=== Performance Comparison (n=40) ===");
long startTime, endTime;
// Iterative
startTime = System.nanoTime();
int iterResult = fibonacciIterative(40);
endTime = System.nanoTime();
System.out.println("Iterative: " + (endTime - startTime) + " ns");
// Memoized recursive
startTime = System.nanoTime();
int memoResult = fibonacciMemoized(40);
endTime = System.nanoTime();
System.out.println("Memoized Recursive: " + (endTime - startTime) + " ns");
// Simple recursive (WARNING: Very slow!)
System.out.println("\n=== Simple Recursive Warning ===");
System.out.println("Simple recursive Fibonacci has O(2^n) time complexity");
System.out.println("For n=40, it makes ~1 billion recursive calls!");
System.out.println("Uncomment below to try (not recommended):");
// startTime = System.nanoTime();
// int recResult = fibonacciRecursive(40);
// endTime = System.nanoTime();
// System.out.println("Simple Recursive: " + (endTime - startTime) + " ns");
System.out.println("\n=== Time Complexity Analysis ===");
System.out.println("Iterative: O(n) time, O(1) space");
System.out.println("Simple Recursive: O(2^n) time, O(n) space");
System.out.println("Memoized Recursive: O(n) time, O(n) space");
System.out.println("\n=== Recursion Tree for fibonacciRecursive(5) ===");
System.out.println(" fib(5)");
System.out.println(" / \\");
System.out.println(" fib(4) fib(3)");
System.out.println(" / \\ / \\");
System.out.println(" fib(3) fib(2) fib(2) fib(1)");
System.out.println(" / \\ / \\ / \\");
System.out.println(" fib(2) fib(1) fib(1)fib(0) fib(1)fib(0)");
System.out.println(" / \\");
System.out.println("fib(1) fib(0)");
System.out.println("\nTotal calls: 15 (exponential growth!)");
}
}
Complete Comparison Table: Recursion vs Iteration
| Aspect | Recursion | Iteration | Winner | Explanation |
|---|---|---|---|---|
| Definition | Function calls itself | Loops repeat code block | Different paradigms | Both achieve repetition differently |
| Termination | Base case condition | Loop condition false | Similar | Both need termination condition |
| Code Readability | High for recursive problems | Medium | Recursion | Recursive code often more elegant for recursive problems |
| Performance | Slower (function call overhead) | Faster | Iteration | Function calls have overhead (stack management) |
| Memory Usage | High (stack frames) | Low | Iteration | Each recursive call uses stack memory |
| Stack Overflow Risk | High | None | Iteration | Deep recursion causes StackOverflowError |
| Problem Suitability | Tree/graph traversal, backtracking | Simple repetition, arrays | Context dependent | Some problems naturally recursive |
| Debugging Difficulty | Harder | Easier | Iteration | Recursion has call stack to track |
| Code Length | Shorter usually | Longer sometimes | Recursion | Recursive solutions can be concise |
| State Management | Implicit (call stack) | Explicit (variables) | Iteration | Iteration gives more control |
| Time Complexity | Can be exponential (Fibonacci) | Usually linear/predictable | Iteration | Recursion can have poor time complexity |
| Compiler Optimization | Tail recursion optimization | Loop optimization | Iteration | Java doesn't optimize tail recursion well |
| Use Cases | Trees, graphs, backtracking, divide & conquer | Arrays, simple counting, linear processing | Both | Choose based on problem nature |
When to Choose Recursion
- Problems with recursive definition (tree, factorial)
- When code clarity is more important than performance
- Depth is limited and predictable
- Backtracking algorithms
- Divide and conquer algorithms
- Parsing nested structures
When to Choose Iteration
- Performance is critical
- Memory is limited
- Depth can be very large
- Simple counting/repetition
- Array/collection processing
- When debugging ease is important
3. Common Recursive Algorithms
1. Factorial (Mathematical)
public class FactorialExample {
// Recursive factorial
public static int factorial(int n) {
// Base case
if (n < 0) {
throw new IllegalArgumentException("Negative input");
}
if (n == 0 || n == 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
// Tail recursive factorial (optimized)
public static int factorialTail(int n) {
return factorialTailHelper(n, 1);
}
private static int factorialTailHelper(int n, int accumulator) {
// Base case
if (n < 0) {
throw new IllegalArgumentException("Negative input");
}
if (n == 0) {
return accumulator;
}
// Tail recursive call (last operation is recursive call)
return factorialTailHelper(n - 1, n * accumulator);
}
public static void main(String[] args) {
System.out.println("Factorial of 5:");
System.out.println("Standard recursion: " + factorial(5));
System.out.println("Tail recursion: " + factorialTail(5));
System.out.println("\nRecursive call trace for factorial(4):");
System.out.println("factorial(4)");
System.out.println(" 4 * factorial(3)");
System.out.println(" 4 * (3 * factorial(2))");
System.out.println(" 4 * (3 * (2 * factorial(1)))");
System.out.println(" 4 * (3 * (2 * 1))");
System.out.println(" 4 * (3 * 2)");
System.out.println(" 4 * 6");
System.out.println(" 24");
}
}
2. Fibonacci Sequence
public class FibonacciExamples {
// 1. Simple recursive (inefficient O(2^n))
public static int fibonacciSimple(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return fibonacciSimple(n - 1) + fibonacciSimple(n - 2);
}
// 2. Memoized recursive (efficient O(n))
public static int fibonacciMemo(int n) {
int[] memo = new int[n + 1];
return fibonacciMemoHelper(n, memo);
}
private static int fibonacciMemoHelper(int n, int[] memo) {
if (n <= 0) return 0;
if (n == 1) return 1;
if (memo[n] != 0) {
return memo[n];
}
memo[n] = fibonacciMemoHelper(n - 1, memo) +
fibonacciMemoHelper(n - 2, memo);
return memo[n];
}
// 3. Tail recursive Fibonacci
public static int fibonacciTail(int n) {
return fibonacciTailHelper(n, 0, 1);
}
private static int fibonacciTailHelper(int n, int a, int b) {
if (n == 0) return a;
if (n == 1) return b;
return fibonacciTailHelper(n - 1, b, a + b);
}
public static void main(String[] args) {
System.out.println("Fibonacci Sequence (first 10 numbers):");
for (int i = 0; i <= 10; i++) {
System.out.println("F(" + i + ") = " + fibonacciMemo(i));
}
System.out.println("\nPerformance Comparison (n=40):");
long start, end;
// Memoized
start = System.nanoTime();
int memoResult = fibonacciMemo(40);
end = System.nanoTime();
System.out.println("Memoized: " + (end - start) + " ns");
// Tail recursive
start = System.nanoTime();
int tailResult = fibonacciTail(40);
end = System.nanoTime();
System.out.println("Tail Recursive: " + (end - start) + " ns");
// Simple (WARNING: Very slow!)
System.out.println("\nSimple recursive for n=40 would take:");
System.out.println("Time: ~1 second (O(2^40) operations)");
System.out.println("Calls: ~1.1 billion recursive calls!");
}
}
3. Tower of Hanoi (Classic Recursive Problem)
public class TowerOfHanoi {
public static void solveHanoi(int n, char source, char auxiliary, char destination) {
// Base case: Only one disk to move
if (n == 1) {
System.out.println("Move disk 1 from " + source + " to " + destination);
return;
}
// Step 1: Move n-1 disks from source to auxiliary
solveHanoi(n - 1, source, destination, auxiliary);
// Step 2: Move nth disk from source to destination
System.out.println("Move disk " + n + " from " + source + " to " + destination);
// Step 3: Move n-1 disks from auxiliary to destination
solveHanoi(n - 1, auxiliary, source, destination);
}
public static void main(String[] args) {
System.out.println("=== Tower of Hanoi Solution ===");
int disks = 3;
System.out.println("Solution for " + disks + " disks:");
solveHanoi(disks, 'A', 'B', 'C');
System.out.println("\n=== Algorithm Analysis ===");
System.out.println("Number of moves: " + ((int)Math.pow(2, disks) - 1));
System.out.println("Time Complexity: O(2^n)");
System.out.println("Space Complexity: O(n) (recursion depth)");
System.out.println("\n=== Recursive Breakdown for 3 Disks ===");
System.out.println("solveHanoi(3, A, B, C)");
System.out.println(" solveHanoi(2, A, C, B)");
System.out.println(" solveHanoi(1, A, B, C) → Move disk 1: A to C");
System.out.println(" Move disk 2: A to B");
System.out.println(" solveHanoi(1, C, A, B) → Move disk 1: C to B");
System.out.println(" Move disk 3: A to C");
System.out.println(" solveHanoi(2, B, A, C)");
System.out.println(" solveHanoi(1, B, C, A) → Move disk 1: B to A");
System.out.println(" Move disk 2: B to C");
System.out.println(" solveHanoi(1, A, B, C) → Move disk 1: A to C");
}
}
4. Binary Search (Divide and Conquer)
public class BinarySearchRecursive {
// Recursive binary search
public static int binarySearch(int[] arr, int target) {
return binarySearchHelper(arr, target, 0, arr.length - 1);
}
private static int binarySearchHelper(int[] arr, int target, int left, int right) {
// Base case: element not found
if (left > right) {
return -1;
}
int mid = left + (right - left) / 2;
// Base case: element found
if (arr[mid] == target) {
return mid;
}
// Recursive cases
if (arr[mid] > target) {
// Search left half
return binarySearchHelper(arr, target, left, mid - 1);
} else {
// Search right half
return binarySearchHelper(arr, target, mid + 1, right);
}
}
// Iterative version for comparison
public static int binarySearchIterative(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] sortedArray = {2, 5, 8, 12, 16, 23, 38, 45, 56, 72, 91};
int target = 23;
System.out.println("=== Binary Search ===");
System.out.println("Array: " + java.util.Arrays.toString(sortedArray));
System.out.println("Target: " + target);
int recursiveResult = binarySearch(sortedArray, target);
int iterativeResult = binarySearchIterative(sortedArray, target);
System.out.println("Recursive result: Index " + recursiveResult);
System.out.println("Iterative result: Index " + iterativeResult);
System.out.println("\n=== Recursive Call Trace (searching for 23) ===");
System.out.println("Initial: left=0, right=10, mid=5 (arr[5]=23) → Found!");
System.out.println("Only one recursive call needed in best case.");
System.out.println("\n=== Time Complexity Analysis ===");
System.out.println("Best case: O(1) - element at middle");
System.out.println("Worst case: O(log n) - divide array in half each time");
System.out.println("Space complexity:");
System.out.println(" Recursive: O(log n) - recursion depth");
System.out.println(" Iterative: O(1) - constant space");
System.out.println("\n=== Divide and Conquer Pattern ===");
System.out.println("1. Divide: Split problem into subproblems");
System.out.println("2. Conquer: Solve subproblems recursively");
System.out.println("3. Combine: Combine subproblem solutions");
}
}
4. Recursion Tree & Call Stack Visualization
Recursion Tree for fibonacciRecursive(4)
Total calls: 9 (Exponential growth: ~O(2^n))
Call Stack Explanation
- Each recursive call creates a stack frame
- Stack frame contains: parameters, local variables, return address
- Stack grows downward in memory
- Base case returns, popping frames from stack
- Stack overflow occurs when stack exceeds memory limit
- Default stack size in Java: 1MB (can be increased with -Xss flag)
Call Stack for factorial(3)
n=1, return=1
n=2, waiting for factorial(1)
Will return: 2 * 1 = 2
n=3, waiting for factorial(2)
Will return: 3 * 2 = 6
calling factorial(3)
public class StackVisualization {
static int depth = 0;
public static void recursiveMethod(int n) {
depth++;
System.out.println(indent(depth) + "Entering recursiveMethod(" + n + ")");
System.out.println(indent(depth) + "Stack depth: " + depth);
// Base case
if (n <= 0) {
System.out.println(indent(depth) + "Base case reached!");
depth--;
return;
}
// Recursive call
recursiveMethod(n - 1);
System.out.println(indent(depth) + "Exiting recursiveMethod(" + n + ")");
depth--;
}
private static String indent(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println("=== Visualizing Recursion Stack ===");
System.out.println("Calling recursiveMethod(3):\n");
recursiveMethod(3);
System.out.println("\n=== Stack Overflow Demonstration ===");
System.out.println("Default stack size: ~1MB (stores about 10000-20000 calls)");
System.out.println("Uncomment below to cause StackOverflowError:");
// recursiveMethod(10000); // This will cause StackOverflowError
System.out.println("\n=== Preventing Stack Overflow ===");
System.out.println("1. Ensure base case is reachable");
System.out.println("2. Convert to iteration for deep recursion");
System.out.println("3. Increase stack size: java -Xss4m ProgramName");
System.out.println("4. Use tail recursion optimization (if supported)");
}
}
5. Tail Recursion & Optimization
Tail recursion is a special form of recursion where the recursive call is the last operation in the function. Some compilers can optimize tail recursion to avoid stack growth.
| Type | Definition | Example | Optimizable? | Java Support |
|---|---|---|---|---|
| Regular Recursion | Recursive call not last operation | return n * fact(n-1); |
No | Standard recursion |
| Tail Recursion | Recursive call is last operation | return factTail(n-1, n*acc); |
Yes (in theory) | Not optimized by Java |
public class TailRecursionExample {
// Regular recursion (NOT tail recursive)
public static int factorialRegular(int n) {
if (n <= 1) return 1;
// Multiplication happens AFTER recursive call returns
return n * factorialRegular(n - 1); // NOT tail recursive
}
// Tail recursive version
public static int factorialTail(int n) {
return factorialTailHelper(n, 1);
}
private static int factorialTailHelper(int n, int accumulator) {
if (n <= 1) return accumulator;
// Recursive call is LAST operation
return factorialTailHelper(n - 1, n * accumulator); // Tail recursive
}
// Regular Fibonacci (NOT tail recursive)
public static int fibonacciRegular(int n) {
if (n <= 1) return n;
// Addition happens AFTER both recursive calls
return fibonacciRegular(n - 1) + fibonacciRegular(n - 2);
}
// Tail recursive Fibonacci
public static int fibonacciTail(int n) {
return fibonacciTailHelper(n, 0, 1);
}
private static int fibonacciTailHelper(int n, int a, int b) {
if (n == 0) return a;
if (n == 1) return b;
// Recursive call is LAST operation
return fibonacciTailHelper(n - 1, b, a + b);
}
public static void main(String[] args) {
System.out.println("=== Tail Recursion Examples ===");
System.out.println("Factorial of 5:");
System.out.println("Regular: " + factorialRegular(5));
System.out.println("Tail: " + factorialTail(5));
System.out.println("\nFibonacci(10):");
System.out.println("Regular: " + fibonacciRegular(10));
System.out.println("Tail: " + fibonacciTail(10));
System.out.println("\n=== Important Note about Java ===");
System.out.println("Java does NOT optimize tail recursion!");
System.out.println("Unlike functional languages (Scala, Haskell)");
System.out.println("Java still creates stack frames for tail calls");
System.out.println("However, writing tail recursive code is still good practice:");
System.out.println("1. Easier to convert to iteration");
System.out.println("2. Clearer intent for optimization");
System.out.println("3. Works in languages that DO optimize it");
System.out.println("\n=== Converting Tail Recursion to Iteration ===");
System.out.println("Tail recursion can be easily converted to iteration:");
System.out.println("while (condition) {");
System.out.println(" accumulator = update(accumulator, n);");
System.out.println(" n = n - 1;");
System.out.println("}");
}
}
public class RecursionToIteration {
// Original recursive factorial
public static int factorialRecursive(int n) {
if (n <= 1) return 1;
return n * factorialRecursive(n - 1);
}
// Converted to iteration
public static int factorialIterative(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
// Tail recursive factorial
public static int factorialTailRecursive(int n) {
return factorialTailHelper(n, 1);
}
private static int factorialTailHelper(int n, int acc) {
if (n <= 1) return acc;
return factorialTailHelper(n - 1, n * acc);
}
// Tail recursion converted to iteration (easy!)
public static int factorialTailToIterative(int n) {
int acc = 1;
while (n > 1) {
acc = n * acc; // Same as recursive parameter update
n = n - 1; // Same as recursive parameter update
}
return acc;
}
// Complex recursion: Tree traversal
static class TreeNode {
int value;
TreeNode left, right;
TreeNode(int value) { this.value = value; }
}
// Recursive tree traversal
public static void traverseTreeRecursive(TreeNode node) {
if (node == null) return;
// Pre-order traversal
System.out.print(node.value + " ");
traverseTreeRecursive(node.left);
traverseTreeRecursive(node.right);
}
// Iterative tree traversal (using stack)
public static void traverseTreeIterative(TreeNode root) {
if (root == null) return;
java.util.Stack stack = new java.util.Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.print(node.value + " ");
// Push right first so left is processed first (stack is LIFO)
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
}
public static void main(String[] args) {
System.out.println("=== Converting Recursion to Iteration ===");
System.out.println("Factorial(5):");
System.out.println("Recursive: " + factorialRecursive(5));
System.out.println("Iterative: " + factorialIterative(5));
System.out.println("Tail to Iterative: " + factorialTailToIterative(5));
System.out.println("\n=== Tree Traversal ===");
TreeNode tree = new TreeNode(1);
tree.left = new TreeNode(2);
tree.right = new TreeNode(3);
tree.left.left = new TreeNode(4);
tree.left.right = new TreeNode(5);
System.out.print("Recursive traversal: ");
traverseTreeRecursive(tree);
System.out.print("\nIterative traversal: ");
traverseTreeIterative(tree);
System.out.println("\n\n=== When to Convert ===");
System.out.println("1. Performance critical code");
System.out.println("2. Deep recursion risk");
System.out.println("3. Memory constraints");
System.out.println("4. Production systems");
System.out.println("\n=== General Conversion Rules ===");
System.out.println("Tail recursion → Simple loop");
System.out.println("Tree recursion → Stack data structure");
System.out.println("Multiple recursion → Multiple stacks/queues");
}
}
6. Best Practices & Common Mistakes
- Missing base case: Infinite recursion → StackOverflowError
- Base case never reached: Incorrect progress toward base case
- Exponential time complexity: Like naive Fibonacci O(2^n)
- Forgetting return statement: In recursive case or base case
- Modifying parameters incorrectly: Not moving toward base case
- Using recursion for simple loops: When iteration is better
Recursion Best Practices
- Always define base case first
- Ensure progress toward base case
- Use memoization for overlapping subproblems
- Consider tail recursion when possible
- Limit recursion depth (use iteration for deep recursion)
- Document recursion assumptions and limits
- Test with edge cases (0, 1, negative, large numbers)
Performance Guidelines
- Use iteration for performance-critical code
- Memoize recursive functions with overlapping subproblems
- Avoid recursion for O(2^n) time complexity
- Set recursion depth limits
- Consider converting recursion to iteration for production
- Profile recursive code with realistic inputs
When to Use Recursion vs Iteration
Use Recursion when: Problem is naturally recursive, code clarity is priority, depth is limited
Use Iteration when: Performance matters, memory is limited, depth can be large, debugging ease needed
public class RecursionChecklist {
// Checklist for writing recursive functions
public static void recursionChecklist() {
System.out.println("=== Recursion Implementation Checklist ===\n");
System.out.println("1. ✅ Identify the Base Case(s)");
System.out.println(" - When does recursion stop?");
System.out.println(" - Handle edge cases (0, 1, empty, null)");
System.out.println(" - Example: if (n <= 1) return 1;");
System.out.println("\n2. ✅ Define Recursive Case");
System.out.println(" - How does problem break into subproblems?");
System.out.println(" - Example: return n * factorial(n-1);");
System.out.println("\n3. ✅ Ensure Progress Toward Base Case");
System.out.println(" - Parameters must change in recursive call");
System.out.println(" - Example: n-1 moves toward n <= 1");
System.out.println("\n4. ✅ Check Time Complexity");
System.out.println(" - Avoid exponential time (O(2^n))");
System.out.println(" - Consider memoization for overlapping subproblems");
System.out.println("\n5. ✅ Check Space Complexity");
System.out.println(" - Each call uses stack memory");
System.out.println(" - Default stack: ~10000-20000 calls max");
System.out.println("\n6. ✅ Test with Edge Cases");
System.out.println(" - Small inputs (0, 1)");
System.out.println(" - Large inputs (check for stack overflow)");
System.out.println(" - Invalid inputs (negative numbers)");
System.out.println("\n7. ✅ Consider Alternatives");
System.out.println(" - Would iteration be better?");
System.out.println(" - Can tail recursion help?");
System.out.println(" - Is memoization needed?");
}
// Example: Well-designed recursive function
public static int power(int base, int exponent) {
// 1. Base cases
if (exponent < 0) {
throw new IllegalArgumentException("Negative exponent not supported");
}
if (exponent == 0) {
return 1; // Base case 1
}
if (exponent == 1) {
return base; // Base case 2
}
// 2. Recursive case with optimization
// Use divide and conquer: x^n = x^(n/2) * x^(n/2)
int halfPower = power(base, exponent / 2);
if (exponent % 2 == 0) {
return halfPower * halfPower; // Even exponent
} else {
return base * halfPower * halfPower; // Odd exponent
}
// Time complexity: O(log n) instead of O(n)
}
public static void main(String[] args) {
recursionChecklist();
System.out.println("\n=== Example: Optimized Power Function ===");
System.out.println("2^10 = " + power(2, 10));
System.out.println("3^5 = " + power(3, 5));
System.out.println("Time complexity: O(log n)");
System.out.println("Space complexity: O(log n) - recursion depth");
System.out.println("\n=== Final Recommendation ===");
System.out.println("For interview questions: Show both recursive and iterative solutions");
System.out.println("For production code: Prefer iteration unless recursion significantly");
System.out.println("improves code clarity and depth is guaranteed to be small.");
}
}