Java course menu
Java Recursion: Base Cases, Recursive Calls and Examples
Learn recursion in Java: methods that call themselves, base cases and recursive cases, factorial, power, digit sum, string reversal, binary search, the call stack and StackOverflowError, and when to use a loop instead.
On this page
A recursive method calls itself. Recursion solves a problem by reducing it to a smaller version of the same problem, until it becomes so small that the answer is obvious.
The two parts of every recursive method#
- A base case that is answered directly, without recursion.
- A recursive case that calls the method with a smaller input, moving towards the base case.
Factorial#
5! = 5 × 4 × 3 × 2 × 1, and 5! = 5 × 4!:
public class Main {
static long factorial(int n) {
if (n <= 1) {
return 1; // base case
}
return n * factorial(n - 1); // recursive case
}
public static void main(String[] args) {
System.out.println(factorial(5));
System.out.println(factorial(20));
}
}120
2432902008176640000long is needed because factorials grow very quickly; 13! already overflows an int.
How it runs: the call stack#
Each call gets its own copy of n in a stack frame. The calls pile up until the base case, then the results flow back:
public class Main {
static void countdown(int n) {
if (n == 0) {
System.out.println("Lift off!");
return;
}
System.out.println("going down " + n);
countdown(n - 1);
System.out.println("coming back " + n);
}
public static void main(String[] args) {
countdown(3);
}
}going down 3
going down 2
going down 1
Lift off!
coming back 1
coming back 2
coming back 3More examples#
public class Main {
static double power(double base, int exp) {
if (exp == 0) return 1;
return base * power(base, exp - 1);
}
static int digitSum(int n) {
if (n < 10) return n;
return n % 10 + digitSum(n / 10);
}
static String reverse(String s) {
if (s.length() <= 1) return s;
return reverse(s.substring(1)) + s.charAt(0);
}
public static void main(String[] args) {
System.out.println(power(2, 10) + " " + power(1.5, 3));
System.out.println(digitSum(4096));
System.out.println(reverse("recursion"));
}
}1024.0 3.375
19
noisrucerBinary search#
Problems that split in half are natural for recursion. Binary search checks the middle of a sorted range and continues in one half:
public class Main {
static int binarySearch(int[] a, int target, int low, int high) {
if (low > high) return -1;
int mid = low + (high - low) / 2;
if (a[mid] == target) return mid;
if (a[mid] < target) return binarySearch(a, target, mid + 1, high);
return binarySearch(a, target, low, mid - 1);
}
public static void main(String[] args) {
int[] nums = {3, 8, 15, 23, 42, 57, 91};
System.out.println(binarySearch(nums, 42, 0, nums.length - 1));
System.out.println(binarySearch(nums, 5, 0, nums.length - 1));
}
}4
-1low + (high - low) / 2 avoids the overflow that (low + high) / 2 can cause for very large arrays.
Fibonacci and repeated work#
A direct translation of fib(n) = fib(n−1) + fib(n−2) recomputes the same values over and over. Remembering results (memoization) or using a loop fixes that:
import java.util.HashMap;
import java.util.Map;
public class Main {
static long fibSlow(int n) {
if (n < 2) return n;
return fibSlow(n - 1) + fibSlow(n - 2);
}
static final Map<Integer, Long> memo = new HashMap<>();
static long fibMemo(int n) {
if (n < 2) return n;
Long cached = memo.get(n);
if (cached != null) return cached;
long result = fibMemo(n - 1) + fibMemo(n - 2);
memo.put(n, result);
return result;
}
public static void main(String[] args) {
System.out.println(fibSlow(20) + " " + fibMemo(20));
System.out.println(fibMemo(80));
}
}6765 6765
23416728348467685fibSlow(40) makes hundreds of millions of calls; fibMemo(80) computes each value once.
StackOverflowError#
Every call uses stack memory. Recursion that goes too deep, or never reaches its base case, ends with a StackOverflowError:
public class Main {
static int depth = 0;
static void forever() {
depth++;
forever(); // no base case
}
public static void main(String[] args) {
try {
forever();
} catch (StackOverflowError e) {
System.out.println("Stack overflow after many calls");
}
}
}Stack overflow after many callsThe exact depth depends on the JVM and its stack size (often several thousand calls). Java does not optimize tail calls, so prefer loops for long, simple sequences.
Recursion or a loop?#
Common mistakes#
Practice#
Practice exercise
Write a recursive int gcd(int a, int b) using Euclid's algorithm (gcd(a, 0) = a, otherwise gcd(b, a % b)) and print gcd(84, 36).
Show solution
public class Gcd {
static int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
public static void main(String[] args) {
System.out.println(gcd(84, 36));
}
}12Practice exercise
Write a recursive int countChar(String s, char c, int index) that counts how many times c appears in s from index onwards, and print the count of 's' in "mississippi".
Show solution
public class CountChar {
static int countChar(String s, char c, int index) {
if (index == s.length()) return 0;
int here = s.charAt(index) == c ? 1 : 0;
return here + countChar(s, c, index + 1);
}
public static void main(String[] args) {
System.out.println(countChar("mississippi", 's', 0));
}
}4Passing an index avoids creating a new substring at every step.
Key takeaway#
Finished this lesson?