Learn Java

Lesson 40Phase 4: Collections and algorithms

Recursion: methods that call themselves

Solving a problem by solving a smaller copy of it: base cases, the call stack, and when recursion beats a loop.

By Updated 50 min

  • recursion
  • base case
  • recursive case
  • call stack
  • factorial
  • Fibonacci

Some problems have a smaller copy of themselves inside. The total of a shopping list is the first price plus the total of the rest of the list. The size of a folder is the size of its own files plus the size of every folder inside it. When you spot that shape, you can write a method that solves the problem by calling itself on the smaller copy. In this lesson you write methods like that, and watch, call by call, what Java does when a method calls itself.

By the end of this lesson
  • Write a recursive method with a base case and a recursive case that moves toward it.
  • Follow the call stack as recursive calls pile up and then return, one frame at a time.
  • Recurse over numbers, arrays, strings and linked lists by trusting the smaller call.
  • Explain what causes a StackOverflowError and fix the method that caused it.
  • Measure why the plain Fibonacci method is slow, and speed it up with memoization.
  • Decide whether recursion or a loop gives the clearer code for a problem.
From before
You can write methods with parameters and return values, and you know that a static method belongs to the class, so main can call it without making an object first (static and final). Every method on this page is static for that reason. Two more ideas come back in this lesson: the stack, last in first out, from stacks and queues, and the chain of nodes from linked lists.

A method that calls itself#

Picture a long queue outside a cinema. You want to know your place in it, but all you can see is the back of the person in front of you. So you tap their shoulder and ask, “What number are you?” They do not know either, so they tap the person in front of them and ask the same question. The question travels forward until it reaches the person at the very front, who needs to ask nobody: they are number 1. Then the answers travel back. Each person hears a number, adds one, and passes the result to the person behind.

Nobody in that queue solved the whole problem. Each person did one small step and handed the rest to someone else, who did the same small step. A method can work the same way: it does a little of the work and calls itself to do the rest. A method that calls itself is a recursive method, and solving a problem this way is called recursion.

The smallest useful recursive method is a countdown. Press Run, then step through slowly with the arrow buttons while you watch the call stack panel.

Countdown.java
1public class Main {
2 public static void main(String[] args) {
3 countdown(3);
4 }
5
6 static void countdown(int n) {
7 if (n == 0) {
8 System.out.println("Liftoff!");
9 return;
10 }
11 System.out.println(n);
12 countdown(n - 1);
13 }
14}
Prints
›3
›2
›1
›Liftoff!

The program prints the numbers 3, 2 and 1, then Liftoff!, each on its own line. Now look at what happened in the call stack. When main calls countdown(3), a box appears for that call, holding n = 3. Then countdown(3) calls countdown(2), and a second box appears with its own n = 2, while the first box stays where it is, waiting. By the time Liftoff! prints there are four countdown boxes at once, with n equal to 3, 2, 1 and 0.

Then the boxes go away in reverse order. countdown(0) prints and returns, so its box disappears and Java is back inside countdown(1), just after the line that made the call. That call has nothing left to do, so it returns as well, and so on back to main.

Key idea

Every call gets its own box on the call stack, with its own copy of the parameters. Four calls to countdown means four separate variables named n, all alive at the same time.

The base case and the recursive case#

Look at countdown again. It has two parts, and every recursive method you write will have the same two.

  • The base case is an input so small that the method can answer without calling itself. For countdown it is n == 0: print Liftoff! and stop. In the cinema queue, it is the person at the front.
  • The recursive case covers every other input. The method does one small piece of the work and calls itself on a smaller input. For countdown, that means printing n and calling countdown(n - 1).

One more rule is hidden in there, and it is the one people forget: every recursive call has to move closer to the base case. countdown subtracts 1 each time, so from any positive start it must reach 0. If it subtracted nothing, or added 1, it would never get there.

Factorial: building the answer on the way back

countdown does its work on the way down, before each call. Most recursive methods do their work on the way back, by using the answer the smaller call returns. The classic example is the factorial. 4 factorial, written 4!, means 4 × 3 × 2 × 1, which is 24.

The smaller copy hides inside: 4! is 4 × 3!, because 3! is 3 × 2 × 1. In the same way 3! is 3 × 2!, and 2! is 2 × 1!. And 1! is just 1. Those sentences are already the whole method. The base case is n == 1, and the recursive case returns n times the factorial of n - 1.

Factorial.java
1public class Main {
2 public static void main(String[] args) {
3 int answer = factorial(4);
4 System.out.println("4! = " + answer);
5 }
6
7 static int factorial(int n) {
8 if (n == 1) {
9 return 1;
10 }
11 return n * factorial(n - 1);
12 }
13}
Prints
›4! = 24

Step through it and watch the stack grow to four factorial boxes, from n = 4 down to n = 1. None of the first three can finish yet. factorial(4) cannot multiply until it knows factorial(3), which needs factorial(2), which needs factorial(1). Only factorial(1) can answer straight away. Then the answers come back, one multiplication per box, and the program prints 4! = 24.

CallWaits forThen returns
factorial(4)factorial(3)4 × 6 = 24
factorial(3)factorial(2)3 × 2 = 6
factorial(2)factorial(1)2 × 1 = 2
factorial(1)Nothing: it is the base case1
The calls start from the top row down. The answers come back from the bottom row up.
Key idea

A recursive method needs a base case that answers without recursing, and a recursive case that calls the method on a smaller input. Every call must move closer to the base case.

Quick check
A method sumTo(n) should add up 1 + 2 + ... + n. Its base case returns 0 when n is 0. Which line should its recursive case be?

What the call stack is doing#

You have been watching boxes appear and disappear. Each time a method is called, Java sets aside a small block of memory for that one call, called a stack frame, and each box in the panel is one frame. The frame holds the call's parameters and local variables, and it remembers where in the caller to carry on once the call returns. The frames of all the calls that have started but not yet finished are kept in order on the call stack.

It is a stack in the same sense as in stacks and queues: last in, first out. A call pushes a new frame on top. When the call returns, its frame is popped off, and the frame underneath, the caller's, carries on from where it paused. The method running right now is always the one on top. (The visualizer lists the frames oldest first, so the top of the stack is the last box, the one marked Running.)

None of this is special to recursion. When main calls a method that calls another method, three frames are stacked up in the same way. In recursion, several of the frames belong to the same method, each with different values inside.

The order of pushing and popping decides the order of everything a recursive method does. This method prints a line before its recursive call and another line after it.

Predict the outputEcho.java
1public class Main {
2 public static void main(String[] args) {
3 echo(3);
4 }
5
6 static void echo(int n) {
7 if (n == 0) {
8 return;
9 }
10 System.out.println("down " + n);
11 echo(n - 1);
12 System.out.println("up " + n);
13 }
14}

What does echo(3) print? Write every line, in order.

The down lines print while the frames are being pushed: 3, then 2, then 1. Each up line has to wait until every call above its frame has returned, so the up lines come out as the frames are popped, in reverse: 1, then 2, then 3. echo(3) was the first frame in and is the last one out.

Trusting the recursion#

Tracing every frame is the best way to see how recursion works, and a slow way to write a recursive method. When programmers write one, they use a habit sometimes called the leap of faith: while writing the recursive case, assume the call on the smaller input already works and returns the right answer. Your only job is to turn that answer into the answer for the input you were given.

Back in the cinema queue, if the person in front of you tells you their correct place, yours is their number plus one. You never need to know how they worked theirs out.

  1. 1Find the smallest input and its answer. That is the base case.
  2. 2Pretend the method already works for any smaller input.
  3. 3Write the recursive case: call the method on a smaller input, and use its answer to build the answer for this input.
  4. 4Check that every call gets smaller, so the calls must reach the base case.

Numbers: counting digits and raising to a power

How many digits does 2026 have? Dividing an int by 10 drops its last digit, because integer division throws the remainder away: 2026 / 10 is 202. That means 2026 has one more digit than 202. The smallest case is a number below 10, which has one digit.

Powers work the same way. 2 to the power 5 means 2 × 2 × 2 × 2 × 2, which is 2 times (2 to the power 4). Any number to the power 0 is 1, and that gives the base case.

Numbers.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(countDigits(2026));
4 System.out.println(power(2, 5));
5 }
6
7 static int countDigits(int n) {
8 if (n < 10) {
9 return 1;
10 }
11 return 1 + countDigits(n / 10);
12 }
13
14 static int power(int base, int exponent) {
15 if (exponent == 0) {
16 return 1;
17 }
18 return base * power(base, exponent - 1);
19 }
20}
Prints
›4
›32

Read countDigits with the leap of faith: if countDigits(202) correctly returns 3, then 1 + countDigits(202) is 4. You can check that by stepping through, and after a few methods like this you will find you no longer need to. The program prints 4 and then 32.

Put the lines in orderDigitSum.java
Put the lines in order so the program adds up the digits of 472 and prints 13 (4 + 7 + 2). n % 10 is the last digit of n, and n / 10 is everything before it.

Tap a line, then tap where it should go. Or use the arrows.

Arrays: carry an index

An array does not get smaller when you pass it to a method, so you need another way to shrink the problem: pass a position along with it. The call sum(prices, 0)means “the total from index 0 to the end”, which is prices[0] plus the total from index 1 to the end. The smallest case is an index that has walked off the end of the array. Nothing is left to add, so that total is 0.

PriceTotal.java
1public class Main {
2 public static void main(String[] args) {
3 int[] prices = {4, 7, 2, 5};
4 int total = sum(prices, 0);
5 System.out.println("Total: " + total);
6 }
7
8 static int sum(int[] numbers, int i) {
9 if (i == numbers.length) {
10 return 0;
11 }
12 return numbers[i] + sum(numbers, i + 1);
13 }
14}
Prints
›Total: 18

Step through and watch the arrow labelled i in the memory view: each new frame starts one index further along the array. There is only ever one array. Every frame holds a reference to it and its own i, so nothing is copied. On the way back the frames add 5 + 0, then 2 + 5, then 7 + 7, then 4 + 14, and the program prints Total: 18.

Hide the index from the caller
Whoever wants a total should not have to know about the index. A common pattern is a short method that starts the recursion, such as static int sum(int[] numbers) { return sumFrom(numbers, 0); }, with the recursive work in a helper method like sumFrom.

Strings: peel off a character

Strings shrink with substring. To reverse a word, take off its first letter, reverse the rest, and stick that first letter on the end. To reverse stop, reverse top to get pot, then add the s. A word with one letter, or none, is its own reverse, so that is the base case.

Reverse.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(reverse("stop"));
4 }
5
6 static String reverse(String word) {
7 if (word.length() <= 1) {
8 return word;
9 }
10 return reverse(word.substring(1)) + word.charAt(0);
11 }
12}
Prints
›pots

In the call stack, each frame's word is one letter shorter than the one before: stop, top, op, p. The last frame returns p, and each frame on the way back adds its own first letter: po, then pot, then pots.

ExerciseCountLetter.java
Finish countChar so it returns how many times letter appears in word, using recursion and no loop. Take the leap of faith: let the recursive call count the letters in word.substring(1), then decide whether the first letter adds one more.

A palindrome check peels off two letters at once. A word reads the same backwards when its first and last letters match and the part in between is a palindrome too. You solved this with a loop in string practice. The recursive version follows that definition line by line.

Palindrome.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(isPalindrome("racecar"));
4 System.out.println(isPalindrome("rocket"));
5 }
6
7 static boolean isPalindrome(String word) {
8 if (word.length() <= 1) {
9 return true;
10 }
11 char first = word.charAt(0);
12 char last = word.charAt(word.length() - 1);
13 if (first != last) {
14 return false;
15 }
16 return isPalindrome(word.substring(1, word.length() - 1));
17 }
18}
Prints
›true
›false

racecar shrinks to aceca, then cec, then e. That is one letter, so the answer is true. rocket stops at the first comparison, because r and t differ, and the method returns false without recursing at all. A method can have more than one base case, and this one has two: a short word, and a mismatch.

Halving: binary search

A recursive call does not have to shrink the problem by just one. Binary search finds a value in a sorted array by looking at the middle element and throwing half of the array away each time. If the target is smaller than the middle element, it can only be in the left half, so search there. If it is bigger, search the right half. The part still worth searching runs from index lo to index hi, and once lo passes hi nothing is left, so the target is not in the array. The middle index is lo + (hi - lo) / 2, which gives the same number as (lo + hi) / 2 but avoids an int overflow on a huge array. You will study binary search properly, including that midpoint, in searching and sorting.

BinarySearch.java
1public class Main {
2 public static void main(String[] args) {
3 int[] scores = {3, 8, 15, 21, 34, 42, 57, 66};
4 System.out.println(find(scores, 42, 0, scores.length - 1));
5 System.out.println(find(scores, 10, 0, scores.length - 1));
6 }
7
8 static int find(int[] sorted, int target, int lo, int hi) {
9 if (lo > hi) {
10 return -1;
11 }
12 int mid = lo + (hi - lo) / 2;
13 if (sorted[mid] == target) {
14 return mid;
15 }
16 if (target < sorted[mid]) {
17 return find(sorted, target, lo, mid - 1);
18 }
19 return find(sorted, target, mid + 1, hi);
20 }
21}
Prints
›5
›-1

In the memory view, the arrows labelled lo and hi close in as each new frame starts. Finding 42 among eight numbers takes two calls, and the program prints 5, the index where 42 sits. Looking for 10, which is missing, takes four calls. The last one gets an empty range, with lo at 2 and hi at 1, so it prints -1. Each call halves the range, so even a million sorted numbers need only about 20 calls, and the call stack never gets deep.

Linked lists: a node, then a smaller list

A linked list has a recursive definition: it is either empty, or a node followed by a smaller linked list. That is why recursion fits linked lists so well. The length of an empty list (a null reference) is 0, and the length of any other list is 1 plus the length of the list that starts at next.

ExerciseListLength.java
Finish length so it returns the number of nodes in a list, using recursion: an empty list (null) has length 0, and any other list is one node plus a shorter list.

When recursion never stops: StackOverflowError#

Go back to the factorial method and ask it for the factorial of 0. Its base case is n == 1, but 0 is already below 1. So factorial(0) calls factorial(-1), which calls factorial(-2), and n runs away from the base case forever. Each call pushes a frame that cannot be popped until the call above it returns, and none of them ever returns.

Main.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("Working out 0!");
4 int answer = factorial(0);
5 System.out.println("0! = " + answer);
6 }
7
8 static int factorial(int n) {
9 if (n == 1) {
10 return 1;
11 }
12 return n * factorial(n - 1);
13 }
14}
Prints
›Working out 0!
!Exception in thread "main" java.lang.StackOverflowError
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.factorial(Main.java:12)
! at Main.main(Main.java:4)
Stops on line 12

StackOverflowError

A method kept calling itself without ever reaching a base case.

The program prints Working out 0! and then stops with a StackOverflowError: the call stack ran out of room. The second println never runs. Drag the progress bar back to the step where Java throws the error and look at the call stack: a tower of factorial frames, each with its own n, deep into the negative numbers. The steps after it show Java leaving those frames one at a time, looking for a catch that never comes.

The visualizer gives up after a few hundred frames, to keep your browser responsive. Real Java has far more room. On the computer used to test this page, this program got more than 25,000 calls deep before the stack ran out, and the exact number changes with the computer, the method and the -Xss setting. The ending is the same either way. Here is the start of what Java prints in a terminal:

Terminal
read along
1$ java Main.java
2Working out 0!
3Exception in thread "main" java.lang.StackOverflowError
4 at Main.factorial(Main.java:12)
5 at Main.factorial(Main.java:12)
6 at Main.factorial(Main.java:12)
7 at Main.factorial(Main.java:12)
8 ...

Java printed about a thousand of those at Main.factorial lines and then stopped listing them, so the report never even gets down to main. When a stack trace shows one method on the same line over and over, read it as a recursion that never reached its base case. Reading stack traces in general is covered in reading errors and debugging.

Two ways to miss the base case

  • There is no base case at all, so nothing ever stops the calls.
  • There is a base case, but the recursive case can jump over it or move away from it. That is what happened to factorial(0): subtracting 1 from 0 moves away from 1.

The fix here is a base case that catches every small input: if (n <= 1). It covers 1, and it covers 0, whose factorial is defined to be 1 as well. With it, factorial(0) returns 1 at once. Negative numbers have no factorial, yet with this fix the method quietly returns 1 for them too. A stricter method would refuse negative input by throwing an exception, which you will learn to do in exception handling.

Fix the recursion, do not hide the error
StackOverflowError is an Error, not an Exception. Java's documentation says an Error signals a serious problem that a sensible program should not try to catch. When you see one, the cure is in your code: find the input that slips past the base case, then fix the base case or the step toward it.
Quick check
This countdown takes two steps at a time. Its base case returns when n is 0, and its recursive case calls countdown(n - 2). Which call ends with a StackOverflowError?

Fibonacci: recursion that repeats itself#

The Fibonacci numbers start with 0 and 1, and each number after that is the sum of the two before it: 0, 1, 1, 2, 3, 5, 8, 13, 21, and on forever. Count the positions from 0, so fib(0) is 0, fib(1) is 1, fib(2) is 1, and fib(6) is 8.

That definition is already recursive: fib(n) is fib(n - 1) + fib(n - 2). It needs two base cases, because the recursive case reaches back two places: fib(0) is 0 and fib(1) is 1. One check covers both, since in both cases the answer is n itself.

Fibonacci.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("fib(5) = " + fib(5));
4 }
5
6 static int fib(int n) {
7 if (n <= 1) {
8 return n;
9 }
10 return fib(n - 1) + fib(n - 2);
11 }
12}
Prints
›fib(5) = 5

This method is different from every one so far, because a single call can make two recursive calls. Step through and keep an eye on the call stack. fib(5) calls fib(4), and that call goes all the way down to fib(1) and fib(0) and back before fib(5) gets to make its second call, fib(3). The stack grows and shrinks many times over.

Predict the outputFibOrder.java
1public class Main {
2 public static void main(String[] args) {
3 fib(4);
4 System.out.println();
5 }
6
7 static int fib(int n) {
8 System.out.print(n + " ");
9 if (n <= 1) {
10 return n;
11 }
12 return fib(n - 1) + fib(n - 2);
13 }
14}

This version prints n at the start of every call. What does fib(4) print?

Drawn out, the calls make a tree. Each call sits above the two calls it makes, and Java visits the tree in a fixed order: a call starts, its whole fib(n - 1) side runs to the end, then its fib(n - 2) side, and only then does it return. Choose n = 5 below and press Play.

The calls fib(5) makes0 of 15 calls
n
543210121032101

Press Next to watch main call fib(5), or Play to let it run.

Call stackmain
0/30
Times each call happens

fib(5) makes 15 calls without the memo and 9 with it.

Not called yetOn the call stackReturned (badge: its value)

While it plays, look at the calls marked as on the call stack. At every moment they form a path from the top of the tree down to the call that is running: the call stack is that path. Then look at how often the same call turns up. Inside fib(6), fib(2) is worked out from scratch 5 times, and fib(1) is called 8 times. Choose n = 6 and use the buttons under the tree to light up every copy of one call.

Counting the calls

To measure how bad the repeating gets, count the calls. The counter has to be a static field outside the method. A local variable lives inside one frame, so every call would get a fresh one that starts at 0.

CountCalls.java
1public class Main {
2 static int calls = 0;
3
4 public static void main(String[] args) {
5 int answer = fib(5);
6 System.out.println("fib(5) = " + answer + " after " + calls + " calls");
7
8 calls = 0;
9 answer = fib(10);
10 System.out.println("fib(10) = " + answer + " after " + calls + " calls");
11 }
12
13 static int fib(int n) {
14 calls++;
15 if (n <= 1) {
16 return n;
17 }
18 return fib(n - 1) + fib(n - 2);
19 }
20}
Prints
›fib(5) = 5 after 15 calls
›fib(10) = 55 after 177 calls

Doubling n from 5 to 10 multiplied the calls by more than ten. The visualizer cannot follow much bigger runs, because it records several steps for every call. Real Java has no such limit, and with larger values of n the same counter prints this:

MoreCalls.java
read along
1public class Main {
2 static int calls = 0;
3
4 public static void main(String[] args) {
5 int[] sizes = {10, 20, 30, 40};
6 for (int n : sizes) {
7 calls = 0;
8 int answer = fib(n);
9 System.out.println("fib(" + n + ") = " + answer + " after " + calls + " calls");
10 }
11 }
12
13 static int fib(int n) {
14 calls++;
15 if (n <= 1) {
16 return n;
17 }
18 return fib(n - 1) + fib(n - 2);
19 }
20}
Output
fib(10) = 55 after 177 calls
fib(20) = 6765 after 21891 calls
fib(30) = 832040 after 2692537 calls
fib(40) = 102334155 after 331160281 calls

Every time n grows by 10, the number of calls is multiplied by more than 120. fib(40) makes over 331 million calls to produce one number, and almost all of them redo work that was already done somewhere else in the tree. fib(50) would need over 40 billion.

Memoization: remember what you already worked out#

The tree shows the fix. The first time you work out fib(3), write the answer down. Every later time something asks for fib(3), read the answer back instead of working it out again. Storing answers like this is called memoization, from memo, a note you write so you remember something, and the place you keep the answers is often called the memo.

An array makes a good memo here, because the inputs are small whole numbers that can be used as indexes: memo[n] holds fib(n) once it is known. A new long array is full of zeros, and fib(n) is never 0 when n is 2 or more, so a 0 in memo[n]means “not worked out yet”. The answers are long because Fibonacci numbers grow fast: fib(47) is already too big for an int.

MemoFibonacci.java
1public class Main {
2 static long[] memo = new long[7];
3 static int calls = 0;
4
5 public static void main(String[] args) {
6 long answer = fib(6);
7 System.out.println("fib(6) = " + answer + " after " + calls + " calls");
8 }
9
10 static long fib(int n) {
11 calls++;
12 if (n <= 1) {
13 return n;
14 }
15 if (memo[n] != 0) {
16 return memo[n];
17 }
18 memo[n] = fib(n - 1) + fib(n - 2);
19 return memo[n];
20 }
21}
Prints
›fib(6) = 8 after 11 calls

Watch the memory view as you step. The arrow labelled n shows which slot the running call is looking at, and memo fills from the left as calls return: memo[2] first, then memo[3], and so on up to memo[6]. When fib(4) makes its second call, fib(2), the answer is already in memo[2], so that call returns at once without calling anything. The program prints fib(6) = 8 after 11 calls, down from 25 without the memo.

Now go back up to the call tree and switch the memo on. The repeated calls fade out, because with the memo they never happen. Each fib(k) is worked out once, and every later request for it is answered from the memo straight away. For n = 7 the tree shrinks from 41 calls to 13. The same counter as before shows how far the saving goes:

MemoCounts.java
1public class Main {
2 static long[] memo;
3 static int calls;
4
5 public static void main(String[] args) {
6 int[] sizes = {10, 20, 30, 40};
7 for (int n : sizes) {
8 memo = new long[n + 1];
9 calls = 0;
10 long answer = fib(n);
11 System.out.println("fib(" + n + ") = " + answer + " after " + calls + " calls");
12 }
13 }
14
15 static long fib(int n) {
16 calls++;
17 if (n <= 1) {
18 return n;
19 }
20 if (memo[n] != 0) {
21 return memo[n];
22 }
23 memo[n] = fib(n - 1) + fib(n - 2);
24 return memo[n];
25 }
26}
Prints
›fib(10) = 55 after 19 calls
›fib(20) = 6765 after 39 calls
›fib(30) = 832040 after 59 calls
›fib(40) = 102334155 after 79 calls
nCalls without the memoCalls with the memo
1017719
2021,89139
302,692,53759
40331,160,28179
From the two counting programs above. With the memo, fib(n) makes 2n - 1 calls.

With the memo, going from fib(20) to fib(40) roughly doubles the work. Without it, the same step multiplies the work by about fifteen thousand.

Key idea

Memoization trades a little memory for a lot of time: store each answer the first time you work it out, and look it up every time after that.

Quick check
In the memo version, someone replaces the last two lines of fib with a plain return fib(n - 1) + fib(n - 2); and keeps the if (memo[n] != 0) check. What happens to fib(6)?

A HashMap as the memo

An array only works when the inputs can be used as indexes. When they cannot, because they are huge numbers, or strings, or pairs of values, keep the answers in a map from HashMap and HashSet, with the input as the key and the answer as the value. Step through it and watch the map in the memory view fill up as each answer is stored.

MapMemo.java
1import java.util.HashMap;
2import java.util.Map;
3
4public class Main {
5 static Map<Integer, Long> memo = new HashMap<>();
6
7 public static void main(String[] args) {
8 System.out.println("fib(50) = " + fib(50));
9 System.out.println("Answers remembered: " + memo.size());
10 }
11
12 static long fib(int n) {
13 if (n <= 1) {
14 return n;
15 }
16 if (memo.containsKey(n)) {
17 return memo.get(n);
18 }
19 long result = fib(n - 1) + fib(n - 2);
20 memo.put(n, result);
21 return result;
22 }
23}
Prints
›fib(50) = 12586269025
›Answers remembered: 49

containsKey asks whether the answer for n is stored, and get hands it back. The map ends up holding 49 answers, one for each n from 2 to 50.

A one-line memo that breaks
You may come across return memo.computeIfAbsent(n, k -> fib(k - 1) + fib(k - 2)); as a shortcut. Do not use it with a HashMap. computeIfAbsent stores the value its lambda works out, and the documentation says that lambda should not change the map. Here the lambda calls fib, which puts entries of its own into the same map, so Java usually notices and throws a ConcurrentModificationException.

Recursion or a loop?#

Anything you can write with recursion you can also write with a loop, and the other way round. This program works out a factorial both ways. Step through it and compare the call stack during the two calls.

BothWays.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(factorialRecursive(5));
4 System.out.println(factorialLoop(5));
5 }
6
7 static int factorialRecursive(int n) {
8 if (n <= 1) {
9 return 1;
10 }
11 return n * factorialRecursive(n - 1);
12 }
13
14 static int factorialLoop(int n) {
15 int result = 1;
16 for (int i = 2; i <= n; i++) {
17 result = result * i;
18 }
19 return result;
20 }
21}
Prints
›120
›120

Both print 120. While factorialRecursive(5) runs, the stack grows to five frames of it. While factorialLoop(5) runs there is only one frame, and result and i change inside it.

Recursion
A loop
Each step is a new call with its own frame.
Each step is another pass in the same frame.
Memory grows with the depth: a thousand calls deep means a thousand frames.
Memory stays the same however many passes it makes.
Too deep, and it stops with StackOverflowError.
Can run for millions of passes.
Reads like the definition when a problem contains smaller copies of itself: Fibonacci, folders inside folders, halving a range.
Reads naturally when you walk straight through a range of numbers, an array or a list.

That gives a sensible rule. When a problem splits into smaller copies of itself, especially two or more of them, recursion usually gives the clearer code. When you are walking through items one at a time, a loop is clearer and has no depth limit.

Depth is the trap to watch. A recursive sum of the numbers from 1 to 10,000 works on real Java, but the same method for 1 to 100,000 overflows the stack with Java's default settings, while a loop adds up a hundred million numbers without trouble. Walking a long linked list recursively has the same problem, because every node costs a frame.

Key idea

Use recursion when a problem contains smaller copies of itself, and a loop when you walk straight through a sequence. Every recursive call holds a stack frame until it returns, so watch how deep it can go.

Quick check
Which of these jobs fits recursion better than a loop?

Three problems made for recursion#

Some problems are awkward with loops and take only a few lines with recursion, because they split into smaller copies of themselves. These three are optional, and each one follows the same recipe as before.

The Tower of Hanoi

You have three pegs, A, B and C, and a pile of disks on peg A, the biggest at the bottom. The goal is to move the whole pile to peg C. You may move only one disk at a time, and you may never put a disk on top of a smaller one.

Planning the moves for even four disks by hand gets confusing. The leap of faith makes it short. To move 3 disks from A to C, first move the top 2 disks out of the way onto B, which is a smaller Hanoi problem. Then move the biggest disk from A to C. Then move the 2 disks from B onto C, another smaller Hanoi problem. Moving 0 disks takes no moves at all, so that is the base case.

Hanoi.java
1public class Main {
2 static int moves = 0;
3
4 public static void main(String[] args) {
5 hanoi(3, 'A', 'C', 'B');
6 System.out.println("Moves: " + moves);
7 }
8
9 static void hanoi(int disks, char from, char to, char spare) {
10 if (disks == 0) {
11 return;
12 }
13 hanoi(disks - 1, from, spare, to);
14 System.out.println("Move disk " + disks + " from " + from + " to " + to);
15 moves++;
16 hanoi(disks - 1, spare, to, from);
17 }
18}
Prints
›Move disk 1 from A to C
›Move disk 2 from A to B
›Move disk 1 from C to B
›Move disk 3 from A to C
›Move disk 1 from B to A
›Move disk 2 from B to C
›Move disk 1 from A to C
›Moves: 7

It prints seven moves and then Moves: 7. Each extra disk doubles the moves and adds one more, so n disks take 2 to the power n, minus 1, moves.

DisksMoves
11
37
101,023
201,048,575
6418,446,744,073,709,551,615

Every order of some letters

In how many orders can you arrange the letters of abc? To list them all, try each letter in the first place, and after each choice list every order of the letters that are left. That inner job is the same problem with one letter fewer. Below, chosen holds the letters placed so far and rest holds the ones still to place. When rest is empty, chosen is one complete arrangement.

Permutations.java
1public class Main {
2 public static void main(String[] args) {
3 permute("", "abc");
4 }
5
6 static void permute(String chosen, String rest) {
7 if (rest.isEmpty()) {
8 System.out.println(chosen);
9 return;
10 }
11 for (int i = 0; i < rest.length(); i++) {
12 char next = rest.charAt(i);
13 String remaining = rest.substring(0, i) + rest.substring(i + 1);
14 permute(chosen + next, remaining);
15 }
16 }
17}
Prints
›abc
›acb
›bac
›bca
›cab
›cba

The six lines are all the permutations of abc, its possible orders. This method has a loop inside it, so each call makes as many recursive calls as there are letters left: 3, then 2, then 1. The count grows fast. 4 letters give 24 orders, and 10 letters give 3,628,800, which is 10!.

Flood fill: the paint bucket

The paint bucket in a drawing program fills the area you click, spreading to every touching pixel of the same color and stopping at edges. On a grid stored in a 2D array, that is a short recursion: to fill a cell, paint it, then fill the four cells next to it, below, above, right and left. The base cases are the cells to skip: anything outside the grid, and anything that is not blank.

FloodFill.java
1public class Main {
2 public static void main(String[] args) {
3 char[][] picture = {
4 ".......".toCharArray(),
5 ".#####.".toCharArray(),
6 ".#...#.".toCharArray(),
7 ".#####.".toCharArray(),
8 ".......".toCharArray()
9 };
10 fill(picture, 0, 0);
11 for (char[] row : picture) {
12 System.out.println(new String(row));
13 }
14 }
15
16 static void fill(char[][] picture, int row, int col) {
17 if (row < 0 || row >= picture.length || col < 0 || col >= picture[row].length) {
18 return;
19 }
20 if (picture[row][col] != '.') {
21 return;
22 }
23 picture[row][col] = '*';
24 fill(picture, row + 1, col);
25 fill(picture, row - 1, col);
26 fill(picture, row, col + 1);
27 fill(picture, row, col - 1);
28 }
29}
Prints
›*******
›*#####*
›*#...#*
›*#####*
›*******

Starting from the top left corner, the paint flows all the way around the box, along every edge of the picture, and never gets inside, because the walls block every way in. The bounds check matters here. The paint reaches the edges of the grid, and without that first if the method would ask for a cell that does not exist and crash with an ArrayIndexOutOfBoundsException. For a big image the recursion could go a million calls deep, so real drawing programs usually fill with a loop and a stack or queue of their own.

Try it

Answer each one in your head or on paper first, then open the answer.

1
In countdown, which part is the base case and which is the recursive case? Why is the recursive case sure to reach the base case for any positive n?
2
What does mystery(4) return? Work it out on paper, then open it and step through.
3
countDigits(-512) returns 1. Why, and how could you fix it?
4
Counting main, how many frames are on the call stack at the deepest point of factorial(4)?
5
In the flood fill program, what would fill(picture, 2, 3) paint instead?

Practice

Work through these on your own. Each one builds on this page.

1
Write static int gcd(int a, int b) using Euclid's rule: gcd(a, 0) is a, and otherwise gcd(a, b) is gcd(b, a % b). Check that gcd(48, 18) is 6, then step through it and write down the arguments in every frame.
2
Using the Node class from the linked list exercise, write static void printBackwards(Node node), which prints the items of a list from the last node to the first. Use recursion and no extra list or array.
3
Write static String toBinary(int n), which returns n written in base 2, so toBinary(13) returns "1101". The last binary digit is n % 2, and the digits before it are toBinary(n / 2). Compare a few answers with Integer.toBinaryString.
4
You can climb a staircase 1 or 2 steps at a time. Write static long ways(int steps), which counts the different ways to reach the top. The ways to climb n steps are the ways to climb n - 1 (your first move was a single step) plus the ways to climb n - 2 (it was a double). There is 1 way to climb 0 steps and 1 way to climb 1 step. Check that 10 steps give 89 ways, then add a memo and work out 80 steps.
5
Write static long fastPower(long base, int exponent) using two facts: when the exponent is even, base to the power n is (base to the power n / 2) squared, and when it is odd, it is base times base to the power n - 1. Anything to the power 0 is 1. Give it and the power method from this page a static call counter, then compare how many calls each makes for an exponent of 1000. Use a base of 1 so the answers do not overflow.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email