Recursion
Learn how functions can call themselves to solve problems that repeat in a self-similar way.
Recursion
Every function we have written so far calls other functions — or just runs its own code. But a function can also call itself. This is called recursion.
function countdown(n) {
if (n === 0) {
console.log("Done!");
return;
}
console.log(n);
countdown(n - 1); // function calls itself
}
countdown(3);
// 3
// 2
// 1
// Done!countdown calls itself with a smaller value each time. Each call reduces n by 1 until it hits 0 — then it stops.
Why Recursion Exists
Some problems are naturally self-similar — they are made up of smaller versions of themselves. Loops can solve many of these, but recursion often expresses the solution more naturally.
Think of a set of Russian dolls. To open all of them you open the outer one, then do the same thing to the smaller one inside, and again to the one inside that — until there is nothing left. That is recursion — doing the same thing to a smaller version of the problem until you reach the simplest case.
The Two Essential Parts
Every recursive function must have exactly two parts. Without either one, it breaks.
1. The Base Case
The condition that stops the recursion. When this is reached, the function returns a value without calling itself again.
2. The Recursive Case
The part where the function calls itself with a simpler or smaller input — moving closer to the base case each time.
function countdown(n) {
if (n === 0) { // ← base case — stop here
console.log("Done!");
return;
}
console.log(n);
countdown(n - 1); // ← recursive case — smaller input each time
}If you forget the base case — or the recursive case never moves toward it — the function calls itself forever. This is called infinite recursion and it crashes your program with a stack overflow error.
How Recursion Works — The Call Stack
Every time a function is called, JavaScript adds it to the call stack — a stack of functions currently running. When a function returns, it is removed from the stack.
With recursion, each call adds a new layer to the stack until the base case is hit — then each layer returns one by one from the bottom up.
function factorial(n) {
if (n === 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
factorial(4);Here is what the call stack looks like:
factorial(4) → 4 * factorial(3)
factorial(3) → 3 * factorial(2)
factorial(2) → 2 * factorial(1)
factorial(1) → returns 1 ← base case reached
Now unwinds:
factorial(2) → 2 * 1 = 2
factorial(3) → 3 * 2 = 6
factorial(4) → 4 * 6 = 24 ← final answerThe calls go down until the base case, then the answers come back up and multiply together.
Classic Example — Factorial
Factorial of a number n means multiplying all integers from n down to 1.
5! = 5 × 4 × 3 × 2 × 1 = 120Notice the pattern — factorial(5) = 5 * factorial(4). The problem contains a smaller version of itself. That is the signal that recursion is a natural fit.
function factorial(n) {
if (n === 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
console.log(factorial(5)); // 120
console.log(factorial(6)); // 720
console.log(factorial(1)); // 1The same thing written as a loop:
function factorialLoop(n) {
let result = 1;
for (let i = n; i >= 1; i--) {
result *= i;
}
return result;
}Both work. The recursive version maps directly to the mathematical definition — n! = n × (n-1)!. Sometimes recursion just expresses the idea more clearly.
Classic Example — Fibonacci
The Fibonacci sequence is a series where each number is the sum of the two before it:
0, 1, 1, 2, 3, 5, 8, 13, 21...fibonacci(n) = fibonacci(n-1) + fibonacci(n-2) — the definition itself is recursive.
function fibonacci(n) {
if (n === 0) return 0; // base case 1
if (n === 1) return 1; // base case 2
return fibonacci(n - 1) + fibonacci(n - 2); // recursive case
}
console.log(fibonacci(0)); // 0
console.log(fibonacci(1)); // 1
console.log(fibonacci(6)); // 8
console.log(fibonacci(8)); // 21Two base cases here — one for 0 and one for 1. The recursion splits into two calls each time, building up the answer from the bottom.
The simple Fibonacci recursion above is a great learning example but it is inefficient for large numbers. fibonacci(40) makes millions of repeated calls. In real applications you would use a loop or a technique called memoization to cache results. We cover this in the Advanced section.
A Real Example — Summing Nested Numbers
Recursion becomes truly useful when dealing with data that has unknown depth — something a simple loop cannot handle cleanly.
Imagine an array that can contain numbers or other arrays of numbers, nested to any depth:
const data = [1, [2, 3], [4, [5, 6]], 7];A loop cannot handle this cleanly because you don't know how deep the nesting goes. Recursion handles it naturally:
function sumAll(input) {
let total = 0;
for (const item of input) {
if (Array.isArray(item)) {
total += sumAll(item); // item is an array — go deeper
} else {
total += item; // item is a number — add it
}
}
return total;
}
console.log(sumAll([1, [2, 3], [4, [5, 6]], 7])); // 28For every item — if it is a number, add it. If it is an array, do the same thing to the inner array. The function does not care how deeply nested the data is — it handles each level the same way.
Recursion vs Loops
Recursion and loops can often solve the same problems. Here is how to decide which to use:
// Countdown — loop version
function countdownLoop(n) {
for (let i = n; i >= 1; i--) {
console.log(i);
}
}
// Countdown — recursive version
function countdownRecursive(n) {
if (n === 0) return;
console.log(n);
countdownRecursive(n - 1);
}| Situation | Use |
|---|---|
| Simple counting or iteration | Loop |
| Unknown or variable depth | Recursion |
| Tree structures, nested data | Recursion |
| The problem's definition is recursive | Recursion |
| Performance is critical | Loop |
| Readability matters more than performance | Recursion |
Loops are generally faster because they don't add to the call stack. Recursion is more natural for problems that are inherently self-similar.
A Real Example — Building a File Path
Imagine building a breadcrumb path by walking up a nested category structure:
const category = {
name: "Arrow Functions",
parent: {
name: "Functions",
parent: {
name: "JavaScript",
parent: null
}
}
};
function buildPath(category) {
if (category.parent === null) {
return category.name; // base case — no parent left
}
return buildPath(category.parent) + " > " + category.name; // recursive case
}
console.log(buildPath(category));
// JavaScript > Functions > Arrow FunctionsThe function walks up the parent chain one level at a time until it hits null — the base case. Then it builds the path on the way back down. A loop would need to know the depth upfront — recursion does not.
Summary
- Recursion is when a function calls itself to solve a smaller version of the same problem
- Every recursive function needs a base case to stop and a recursive case to continue
- Each call is added to the call stack — when the base case is hit, the stack unwinds back up
- Without a base case, recursion runs forever and causes a stack overflow
- Recursion is most powerful for problems with unknown depth — nested data, trees, and self-similar structures
- Loops are faster — recursion is more readable for naturally recursive problems
- Always make sure each recursive call moves closer to the base case