Recursion
Recursion is when a function calls itself. While this might sound strange at first, it's a powerful technique for solving problems that can be broken down into smaller, similar sub-problems.
⚠️ WARNING: This is a tricky topic, so take your time to understand it fully!
Basic Recursion Example
Let's start with a simple recursive function that prints numbers:
function printNumbers(number) {
if (number > 10) {
return // Base case: stop the recursion
}
console.log(number)
printNumbers(number + 1) // Recursive call: function calls itself
}
printNumbers(1) // Prints: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
This replaces what you might do with a for loop:
// For loop equivalent
for (let i = 1; i <= 10; i++) {
console.log(i)
}
Two Essential Parts of Recursion
Every recursive function needs these two components:
1. Base Case (Stopping Condition)
The condition that stops the recursion from continuing forever:
if (number > 10) {
return // This stops the recursion
}
Without a proper base case, you get infinite recursion and a stack overflow error:
// ❌ This will crash!
function printHi() {
console.log("Hi")
printHi() // RangeError: Maximum call stack size exceeded
}
printHi()
2. Recursive Case (Self-Call)
The function calling itself with modified parameters:
printNumbers(number + 1) // Calls itself with the next number
Understanding the Call Stack
When a function calls itself, each call gets added to the call stack:
function countdown(n) {
console.log(n)
if (n <= 0) return
console.log("Before")
countdown(n - 1)
console.log("After")
}
countdown(3)
// Output:
// 3
// Before
// 2
// Before
// 1
// Before
// 0
// After
// After
// After
Call stack visualization:
countdown(3) calls countdown(2)
countdown(2) calls countdown(1)
countdown(1) calls countdown(0)
countdown(0) returns
countdown(1) returns
countdown(2) returns
countdown(3) returns
When to Use Recursion
Tree-like Data Structures
const fileSystem = {
name: "root",
type: "folder",
children: [
{
name: "documents",
type: "folder",
children: [
{ name: "resume.pdf", type: "file" },
{ name: "notes.txt", type: "file" },
],
},
{
name: "photos",
type: "folder",
children: [{ name: "vacation.jpg", type: "file" }],
},
{ name: "readme.txt", type: "file" },
],
}
// Count all files in the file system
function countFiles(item) {
// Base case: if it's a file, count it
if (item.type === "file") return 1
// Recursive case: if it's a folder, count files in all children
let count = 0
for (const child of item.children) {
count += countFiles(child) // Recursive call for each child
}
return count
}
console.log(countFiles(fileSystem)) // 4 files total
Common Mistakes
Forgetting the Base Case
// ✅ Good - has base case function countdown(n) { if (n <= 0) return // Base case console.log(n) countdown(n - 1) } // ❌ Bad - no base case function badCountdown(n) { console.log(n) badCountdown(n - 1) // This will run forever! }Not Moving Toward the Base Case
// ✅ Good - n gets smaller each time function countdown(n) { if (n <= 0) return console.log(n) countdown(n - 1) // Moving toward base case } // ❌ Bad - n never changes function badCountdown(n) { if (n <= 0) return console.log(n) countdown(n) // n never changes! }
When to Use Recursion
✅ Use recursion when:
- Working with tree-like or nested data structures
- The problem can be broken into smaller, similar sub-problems
- The recursive solution is clearer than the iterative one
- Processing hierarchical data (file systems, organizational charts, etc.)
❌ Avoid recursion when:
- A simple loop would work just as well
- Performance is critical (recursion has overhead)
- The recursive solution is hard to understand
Exercise
Write a recursive function that finds the maximum value in a nested array structure like this:
const data = [1, [2, 3], [4, [5, 6]], 7]
// Should return 7
// You can use Array.isArray() to check if an item is an array
Solution
function findMax(arr) {
let max = -Infinity
for (const item of arr) {
if (Array.isArray(item)) {
// Recursive case: if item is an array, find max in that array
const subMax = findMax(item)
max = Math.max(max, subMax)
} else {
// Base case: if item is a number, compare with current max
max = Math.max(max, item)
}
}
return max
}
const data = [1, [2, 3], [4, [5, 6]], 7]
console.log(findMax(data)) // 7