Recursion is a problem-solving technique where a function calls itself (or executes code that eventually leads to calling itself). At its core, it’s a straightforward concept. However, recursion has many pitfalls. For example, this code will run infinitely until stopped by an external limitation (power, memory, terminated, stack overflow, etc.):

Note: Python and Java have a stack limit of ~1k and ~10k, respectively, meaning a function can only call itself 1k or 10k levels deep before your program breaks. C++ has a memory limit of ~1 MB instead of a stack limit.

Recursion is typically only used to solve problems with sub-problems. Every recursive call should bring the computer one step closer to solving the problem by breaking it down into sub-problems, and then breaking the sub-problems down into more sub-problems, until eventually the sub-problems are so simple that we know the answer. Then, once we have the solutions to the small sub-problems, we can build back up solutions to higher sub-problems until we know the solution to the original problem.

To do this, a good recursive function should have two main cases that define how it should call itself so that no stack overflows or infinite recursions occur:

Base case - This is a simple case of the problem that we know the solution of immediately. All recursive cases must reduce to a base case. Note that you can have more than one base case.

Recursive case - This is every other case of the problem. We might not know the solution to it, but we should know how to express the solution in terms of its sub-problems. This is the case where the function will call itself. Remember that eventually, all subproblems should reduce to the base case.

The flow of a recursive function that uses the two cases must be:

First, check if we have reached a base case. If we have, then return the base case solution. If we haven’t, proceed to the recursive case.

The recursive case is executed, and it should break down the problem into smaller pieces that will eventually all reach the base case.

Let’s look at an example where we need to find the

n

th Fibonacci number. The first two Fibonacci numbers are 0 and 1. Then, every subsequent number is equal to the sum of the previous two numbers. The Fibonacci sequence would then be 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Because every Fibonacci number can be defined as the sum of 2 earlier Fibonacci numbers, we can treat the earlier numbers as sub-problems. So, our cases would be:

Base cases - the first Fibonacci number is 0, and the second Fibonacci number is 1

Recursive case - the

n

th Fibonacci number is the sum of the

n-1

th Fibonacci number and the

n-2

th Fibonacci number.

We know that if we keep defining a Fibonacci number in terms of the two previous Fibonacci numbers, then we define those Fibonacci numbers in terms of their two previous Fibonacci numbers, we will eventually reach the first or second Fibonacci number. So, we can write a function that tells us the

n

th Fibonacci number like so:

Code:

def fibonacci_number(n):
# base case 1
if n == 1:
return 0
# base case 2
if n == 2:
return 1
# recursive case
return fibonacci_number(n - 1) + fibonacci_number(n - 2)
# Unit tests to make sure our code is working
print(fibonacci_number(1))
print(fibonacci_number(2))
print(fibonacci_number(3))
print(fibonacci_number(4))
print(fibonacci_number(5))
print(fibonacci_number(6))
print(fibonacci_number(7))
print(fibonacci_number(8))

Output:

> 0
> 1
> 1
> 2
> 3
> 5
> 8
> 13

Code:

public class FibonacciNumbers{
public static void main(String []args){
// Unit tests to make sure our code is working
System.out.println(fibonacci_number(1));
System.out.println(fibonacci_number(2));
System.out.println(fibonacci_number(3));
System.out.println(fibonacci_number(4));
System.out.println(fibonacci_number(5));
System.out.println(fibonacci_number(6));
System.out.println(fibonacci_number(7));
System.out.println(fibonacci_number(8));
}
static int fibonacci_number(int n) {
// base case 1
if(n == 1) {
return 0;
}
// base case 2
if(n == 2) {
return 1;
}
// recursive case
return fibonacci_number(n - 1) + fibonacci_number(n - 2);
}
}

Output:

> 0
> 1
> 1
> 2
> 3
> 5
> 8
> 13

Code:

#include <iostream>
using namespace std;
int fibonacci_number(int n) {
// base case 1
if(n == 1) {
return 0;
}
// base case 2
if(n == 2) {
return 1;
}
// recursive case
return fibonacci_number(n - 1) + fibonacci_number(n - 2);
}
int main() {
// Unit tests to make sure our code is working
cout << fibonacci_number(1) << "\n";
cout << fibonacci_number(2) << "\n";
cout << fibonacci_number(3) << "\n";
cout << fibonacci_number(4) << "\n";
cout << fibonacci_number(5) << "\n";
cout << fibonacci_number(6) << "\n";
cout << fibonacci_number(7) << "\n";
cout << fibonacci_number(8) << "\n";
return 0;
}

Output:

> 0
> 1
> 1
> 2
> 3
> 5
> 8
> 13

The recursion you have seen thus far (where a function calls itself) is called direct recursion. A secondary type of recursion is indirect recursion, where a function calls some other functions that eventually lead back to calling itself.

Note: Tail recursion, where no code is executed after a function’s recursive call, often means that you don’t need to use recursion and can instead build the solution iteratively. This approach is called dynamic programming and is covered in a later article.

Recursive functions are an essential algorithmic technique, with applications everywhere from DFS (which you’ll see in a future article!) to the AP CS A exam.

You can play with all the code we've used in this article on Replit: