Recursion

Methods calling themselves

7 min read

Recursion in Java

Recursion is when a method calls itself. Every recursive method needs a base case (stopping condition) and a recursive case (calls itself with simpler input).

Anatomy of Recursion

  • Base Case: Condition that stops recursion
  • Recursive Case: Method calls itself with smaller problem
  • Progress: Each call must move toward base case

⚠️ Danger: Missing base case = StackOverflowError! Always ensure recursion terminates.

🔑 Memory: Each recursive call adds a frame to the call stack. Deep recursion can exhaust stack memory.

✓ Tip: Tail recursion (recursive call is last operation) can be optimized by some compilers (not Java though).

Code Examples

Factorial - classic recursion example

java
1// Classic: Factorial (n! = n * (n-1)!)
2public int factorial(int n) {
3    // Base case
4    if (n <= 1) {
5        return 1;
6    }
7    // Recursive case
8    return n * factorial(n - 1);
9}
10
11// Trace: factorial(5)
12// 5 * factorial(4)
13// 5 * 4 * factorial(3)
14// 5 * 4 * 3 * factorial(2)
15// 5 * 4 * 3 * 2 * factorial(1)
16// 5 * 4 * 3 * 2 * 1 = 120
17
18System.out.println(factorial(5));  // 120
19System.out.println(factorial(0));  // 1

Fibonacci with memoization optimization

java
1// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13...
2public int fibonacci(int n) {
3    if (n <= 1) return n;  // Base cases: fib(0)=0, fib(1)=1
4    return fibonacci(n - 1) + fibonacci(n - 2);
5}
6
7// WARNING: Naive Fibonacci is O(2^n) - very slow!
8// Use memoization or iteration for large n
9
10// Memoized Fibonacci (much faster!)
11private Map<Integer, Integer> memo = new HashMap<>();
12
13public int fibMemo(int n) {
14    if (n <= 1) return n;
15    if (memo.containsKey(n)) return memo.get(n);
16    
17    int result = fibMemo(n - 1) + fibMemo(n - 2);
18    memo.put(n, result);
19    return result;
20}

Practical recursion: arrays and strings

java
1// Sum of array elements recursively
2public int sumArray(int[] arr, int index) {
3    if (index >= arr.length) return 0;  // Base case
4    return arr[index] + sumArray(arr, index + 1);
5}
6
7// Binary search recursively
8public int binarySearch(int[] arr, int target, int left, int right) {
9    if (left > right) return -1;  // Not found
10    
11    int mid = left + (right - left) / 2;
12    
13    if (arr[mid] == target) return mid;
14    else if (arr[mid] > target)
15        return binarySearch(arr, target, left, mid - 1);
16    else
17        return binarySearch(arr, target, mid + 1, right);
18}
19
20// String reversal
21public String reverse(String str) {
22    if (str.isEmpty()) return str;
23    return reverse(str.substring(1)) + str.charAt(0);
24}
25
26System.out.println(reverse("hello")); // "olleh"

Tower of Hanoi and practical examples

java
1// Tower of Hanoi - classic recursive algorithm
2public void towerOfHanoi(int n, char from, char to, char aux) {
3    if (n == 1) {
4        System.out.println("Move disk 1 from " + from + " to " + to);
5        return;
6    }
7    towerOfHanoi(n - 1, from, aux, to);
8    System.out.println("Move disk " + n + " from " + from + " to " + to);
9    towerOfHanoi(n - 1, aux, to, from);
10}
11
12// Directory traversal (practical use case)
13public void listFiles(File dir, String indent) {
14    if (!dir.exists()) return;
15    
16    System.out.println(indent + dir.getName());
17    
18    if (dir.isDirectory()) {
19        for (File file : dir.listFiles()) {
20            listFiles(file, indent + "  ");
21        }
22    }
23}
24
25// Recursion vs Iteration comparison
26// Iterative factorial (usually preferred)
27public int factorialIterative(int n) {
28    int result = 1;
29    for (int i = 2; i <= n; i++) {
30        result *= i;
31    }
32    return result;
33}

Use Cases

  • Tree/graph traversal (DFS)
  • Divide and conquer algorithms (merge sort, quicksort)
  • Mathematical calculations (factorial, fibonacci)
  • Directory/file system navigation
  • Parsing nested structures (JSON, XML)
  • Backtracking problems (sudoku, N-queens)

Common Mistakes to Avoid

  • Forgetting the base case (infinite recursion)
  • Base case never reached due to logic error
  • Not reducing problem size in recursive call
  • Using recursion when iteration is simpler
  • Not considering stack overflow for deep recursion
  • Inefficient recursion without memoization