Recursion
Recursion
"To understand recursion, you must first understand recursion — but seriously, it's just a function that calls itself to solve a smaller version of the same problem."
Part 1: The Two Rules of Recursion
Every recursive function must have exactly two things. Miss either one and your program will crash with a stack overflow.
1. Base Case (the stop condition)
The condition that ends the recursion. Without it, the function calls itself forever. This is the most important part.
2. Recursive Case (the reduction)
The part where the function calls itself with a smaller input — moving closer to the base case each time.
The simplest example: countdown
function countdown(n) {
if (n <= 0) { // Base case — stop here
console.log("Done!");
return;
}
console.log(n); // Do some work
countdown(n - 1); // Recursive case — smaller problem
}
countdown(5);
// Output: 5 4 3 2 1 Done!Part 2: Classic Examples
Factorial — n!
5! = 5 × 4 × 3 × 2 × 1 = 120. The key insight: factorial(5) = 5 × factorial(4). Every step is the same problem but one size smaller.
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(1)); // 1
console.log(factorial(0)); // 1
// How it resolves:
// factorial(5)
// 5 * factorial(4)
// 4 * factorial(3)
// 3 * factorial(2)
// 2 * factorial(1)
// 1
// = 5 * 4 * 3 * 2 * 1 = 120Fibonacci
Each Fibonacci number is the sum of the two before it: 0, 1, 1, 2, 3, 5, 8, 13… This is a classic recursion example — but watch out, the naive version is slow (O(2ⁿ)).
function fib(n) {
if (n <= 1) return n; // Base cases: fib(0)=0, fib(1)=1
return fib(n - 1) + fib(n - 2); // Recursive case
}
console.log(fib(6)); // 8
console.log(fib(0)); // 0
console.log(fib(1)); // 1
// fib(6) = fib(5) + fib(4) = 5 + 3 = 8Sum of digits
function sumDigits(n) {
if (n < 10) return n; // Single digit — base case
return (n % 10) + sumDigits(Math.floor(n / 10));
}
console.log(sumDigits(1234)); // 10 (1+2+3+4)
console.log(sumDigits(99)); // 18 (9+9)Part 3: The Call Stack
Every time a function is called, JavaScript pushes a frame onto the call stack. When it returns, that frame is popped off. With recursion, many frames stack up before any of them resolve.
Visualizing the call stack for factorial(3)
// factorial(3) is called
// → pushes factorial(3) onto stack
// → calls factorial(2)
// → pushes factorial(2) onto stack
// → calls factorial(1)
// → pushes factorial(1) onto stack
// → returns 1 ← stack unwinds
// ← factorial(2) returns 2 * 1 = 2
// ← factorial(3) returns 3 * 2 = 6
console.log(factorial(3)); // 6Stack Overflow — too many recursive calls
If the base case is missing or never reached, the call stack grows until JavaScript throws a RangeError: Maximum call stack size exceeded.
// ❌ No base case — infinite recursion
function broken(n) {
return broken(n - 1); // Never stops!
}
// RangeError: Maximum call stack size exceeded
// ✅ Always have a base case that is reachable
function fixed(n) {
if (n <= 0) return 0;
return fixed(n - 1);
}Recursion vs Iteration — when to use which?
// Both compute the same thing:
// Iterative factorial
function factIter(n) {
let result = 1;
for (let i = 2; i <= n; i++) result *= i;
return result;
}
// Recursive factorial
function factRec(n) {
if (n <= 1) return 1;
return n * factRec(n - 1);
}
// Use recursion when the problem is naturally tree-shaped
// (e.g., file systems, binary trees, subsets).
// Use iteration for simple linear repetition — it's faster.