Introduction to Recursion
A function that calls itself
Print 3, 2, 1 without a loop. A function that prints 3 can hand the rest, 2 and 1, to a call of itself; that call prints 2 and hands over 1; and a call with nothing left to print stops. A function that calls itself is recursive, and two things make one work:
- A base case: an input small enough to answer without another call.
- A smaller call: every other input calls the function on something smaller, closer to the base case.
Each call waits for the one it made, and the computer keeps the waiting calls in a stack, one frame per call. This lesson shows what goes on that stack, why the place of a print decides the order of the output, how a value travels back, and how deep each language lets you go on our judge.

A base case and a smaller call
down(n) prints n, then calls itself on n − 1. The calls go 3, 2, 1, 0, and 0 is the base case: it returns without another call, so the chain stops:
void down(int n) {
if (n == 0) return; // the base case: stop
cout << n << "\n";
down(n - 1); // a smaller call
}
// down(3) prints 3, 2, 1def down(n):
if n == 0: # the base case: stop
return
print(n)
down(n - 1) # a smaller call
# down(3) prints 3, 2, 1static void down(int n) {
if (n == 0) return; // the base case: stop
System.out.println(n);
down(n - 1); // a smaller call
}
// down(3) prints 3, 2, 1function down(n) {
if (n === 0) return; // the base case: stop
console.log(n);
down(n - 1); // a smaller call
}
// down(3) prints 3, 2, 1Take away the base case and nothing stops the calls; make the call on n instead of n − 1 and it never gets closer. Both run until the language runs out of room for calls, as the bugs below show.
Each call gets its own frame
When down(3) calls down(2), down(3) isn't finished: it waits at its call, and its own n, 3, waits with it. down(2) gets a fresh frame with an n of its own, 2, so the two never mix. The frames sit on the call stack, newest on top, and only the top one runs. When a call returns, its frame comes off and the call under it carries on from the line after its call.
So down(3) makes four frames at its deepest, n = 3, 2, 1 and 0, then they come off in the opposite order: 0 first, 3 last.
Before the call or after it
Move the print below the call and the order turns around. up(n) calls itself first and prints afterwards, so nothing prints until the base case is reached; then each frame prints its own n as it comes off, smallest first:
void up(int n) {
if (n == 0) return;
up(n - 1); // first everything below n
cout << n << "\n"; // then n, on the way back
}
// up(3) prints 1, 2, 3def up(n):
if n == 0:
return
up(n - 1) # first everything below n
print(n) # then n, on the way back
# up(3) prints 1, 2, 3static void up(int n) {
if (n == 0) return;
up(n - 1); // first everything below n
System.out.println(n); // then n, on the way back
}
// up(3) prints 1, 2, 3function up(n) {
if (n === 0) return;
up(n - 1); // first everything below n
console.log(n); // then n, on the way back
}
// up(3) prints 1, 2, 3
Work before the call runs on the way down; work after it runs on the way back. Do both and you get the way down and the way back, the shape of Number Series Without Loops:
void both(int n) {
if (n == 0) {
cout << 0 << "\n";
return;
}
cout << n << "\n"; // on the way down
both(n - 1);
cout << n << "\n"; // and on the way back
}
// both(3) prints 3, 2, 1, 0, 1, 2, 3def both(n):
if n == 0:
print(0)
return
print(n) # on the way down
both(n - 1)
print(n) # and on the way back
# both(3) prints 3, 2, 1, 0, 1, 2, 3static void both(int n) {
if (n == 0) {
System.out.println(0);
return;
}
System.out.println(n); // on the way down
both(n - 1);
System.out.println(n); // and on the way back
}
// both(3) prints 3, 2, 1, 0, 1, 2, 3function both(n) {
if (n === 0) {
console.log(0);
return;
}
console.log(n); // on the way down
both(n - 1);
console.log(n); // and on the way back
}
// both(3) prints 3, 2, 1, 0, 1, 2, 3Returning a value
total(n) adds up 1 to n: total(3) is 1 + 2 + 3 = 6. The base case answers 0. Every other call can't answer until its smaller call does: total(3) is 3 + total(2), so it calls and waits. total(0) returns 0 to total(1), which works out 1 + 0 and returns 1 to total(2), which returns 3 to total(3), which returns 6. A value goes back one frame at a time, only to the call that asked for it:
long long total(int n) {
if (n == 0) return 0; // the base case answers at once
return n + total(n - 1); // waits for the smaller call, then adds n
}
// total(3) returns 6, total(10) returns 55def total(n):
if n == 0: # the base case answers at once
return 0
return n + total(n - 1) # waits for the smaller call, then adds n
# total(3) returns 6, total(10) returns 55static long total(int n) {
if (n == 0) return 0; // the base case answers at once
return n + total(n - 1); // waits for the smaller call, then adds n
}
// total(3) returns 6, total(10) returns 55function total(n) {
if (n === 0) return 0; // the base case answers at once
return n + total(n - 1); // waits for the smaller call, then adds n
}
// total(3) returns 6, total(10) returns 55Step through the call stack
Pick a function and an n, then step through it: the code line running now, the frames on the stack, and what has printed or returned. Before each value appears, say which it will be; the top frame always has the answer.

Returning a list, as the problems do
The practice problems ask you to return the numbers instead of printing them. Pass one list down through the calls and add to it where the print was: after the call, so 1 to n come out in order:
void oneTo(int n, vector<int>& out) { // &: every call fills the same vector
if (n == 0) return;
oneTo(n - 1, out); // first 1 to n - 1
out.push_back(n); // then n
}
vector<int> print1ToN(int n) {
vector<int> out;
oneTo(n, out);
return out;
}
// print1ToN(5) returns [1, 2, 3, 4, 5]def one_to(n, out):
if n == 0:
return
one_to(n - 1, out) # first 1 to n - 1
out.append(n) # then n
def print_1_to_n(n):
out = []
one_to(n, out)
return out
# print_1_to_n(5) returns [1, 2, 3, 4, 5]static void oneTo(int n, List<Integer> out) {
if (n == 0) return;
oneTo(n - 1, out); // first 1 to n - 1
out.add(n); // then n
}
static List<Integer> print1ToN(int n) {
List<Integer> out = new ArrayList<>();
oneTo(n, out);
return out;
}
// print1ToN(5) returns [1, 2, 3, 4, 5]function oneTo(n, out) {
if (n === 0) return;
oneTo(n - 1, out); // first 1 to n - 1
out.push(n); // then n
}
function print1ToN(n) {
const out = [];
oneTo(n, out);
return out;
}
// print1ToN(5) returns [1, 2, 3, 4, 5]Every call adds to the same list, because the list is passed, not copied: Python, Java and JavaScript do that by themselves, and in C++ the & does it. Add before the call instead and the list comes out n down to 1, the order print N to 1 using recursion asks for.
How deep it can go
A recursion n calls deep holds n frames at once, and each language caps how many fit. The deepest total(n) our judge ran, each language at its defaults:
| Language | Deepest that ran | Deeper than that |
|---|---|---|
| Python | 997 | RecursionError: a limit of 1,000 calls |
| Java | 10,000 to 11,526 over 3 runs | StackOverflowError: the stack is full |
| JavaScript | 10,450 | RangeError: the stack is full |
| C++ | 100,000,000, the most tried | no stack used up: that many frames can't fit in memory, so the compiler made it a loop |
Two problems in this group go past Python's limit: Recursion via Repeated Printing asks for n up to 1,000, and Sum of First N Natural Numbers for n up to 10,000; a recursion 1,000 or 10,000 calls deep raised RecursionError on our judge. Raising the limit at the top of a Python solution fixes both:
import sys
sys.setrecursionlimit(20_000) # room for 10,000 calls, and the judge's own
def total(n):
if n == 0:
return 0
return n + total(n - 1)
print(total(10_000))
It printed 50,005,000.
10,000 calls is near the edge in Java and JavaScript too. JavaScript ran total(10_000) in all 3 runs, about 450 calls short of its limit, and Node sets that limit when it starts (JavaScript for DSA). Java ran it in 4 of 5 runs, with a limit that moved between 10,000 and 11,526 from run to run; Java for DSA shows how to give a recursion a bigger stack. C++ didn't stop this recursion at all, because the compiler made it a loop; it can't do that for every recursion, and C++ for DSA measured where a real one ran out.
Every function in this lesson could be a loop, which keeps one frame however long it runs: O(1) space against recursion's O(n). Recursion earns its place when the problem nests: trees, input inside brackets, and the choices of backtracking, where Thinking Recursively picks up.
Bugs that cost a submission
A step that jumps over the base case.
def skip(n):
if n == 0:
return
print(n)
skip(n - 2) # 3, 1, -1, -3, ...: never 0
skip(3)
It printed 3, 1, −1, −3 and on, then raised RecursionError: counting down by 2 from an odd number jumps over 0, so the base case never runs. if n <= 0: catches every number at or below 0, and the same call then printed 3, 1 and stopped.

n-- in the call.
function down(n) {
if (n === 0) return;
console.log(n);
down(n--); // n-- hands over n, then lowers it
}
down(3);
It printed 3 over and over, then threw RangeError: n-- hands the call the old n and lowers it afterwards, so every call gets 3 again. The same happens in Java and C++. Write down(n - 1).
A recursive call whose answer is dropped.
def total(n):
if n == 0:
return 0
n + total(n - 1) # worked out, then thrown away
print(total(3))
It raised TypeError: total(1) worked out 1 + 0 and threw it away, so it returned nothing, None in Python, and total(2) tried to add 2 and None. Every path through a function that answers needs a return.
Check yourself
Quiz
1 / 6Multiple choice
up(n) calls up(n - 1) first and prints n after it. What does up(3) print?Multiple choice
down(n) stops the calls?Multiple choice
total(4) return?Multiple choice
total(3) waits for total(2), where is its n = 3 kept?Multiple choice
Multiple choice
down(n--) pass to the call?Practice problems
This group's problems, in order:
- Recursion via Repeated Printing, print 1 to N using recursion and print N to 1 using recursion: the number added after the call or before it.
- Number Series Without Loops: work on both sides of the call.
- Sum of First N Natural Numbers and Computing Factorial Recursively: a value returned through every frame. In Python, raise the limit for n = 10,000.
- Reverse an Array using recursion: swap the two ends, then the middle is the smaller call.
- Power of a Number Using Recursion, Sum of Digits Using Recursion, Reverse a String Using Recursion and GCD Using Recursion: one smaller call each.
Thinking Recursively, in Recursion & Backtracking, builds on this: trusting the smaller call to do its part.