Recursion
Table of Contents
Introduction
Recursion is a programming technique where a function calls itself to solve a problem by breaking it down into smaller, similar subproblems. Think of Russian nesting dolls—each doll contains a smaller version of itself until you reach the smallest one that doesn’t open.
Recursion is fundamental to computer science and appears in tree traversals, sorting algorithms, mathematical computations, and backtracking problems. While it can seem magical at first, understanding recursion unlocks powerful problem-solving techniques.
Review Functions and Big-O Notation first if needed. Recursion is heavily used in DFS and tree algorithms.
What is recursion?
A recursive function has two essential components:
1. Base Case
The stopping condition that prevents infinite recursion.
if (n === 0) return 1; // Base case
2. Recursive Case
The function calling itself with a simpler input.
return n * factorial(n - 1); // Recursive case
Anatomy of recursion:
factorial(4)
→ 4 * factorial(3)
→ 3 * factorial(2)
→ 2 * factorial(1)
→ 1 * factorial(0)
→ 1 (base case)
← 1
← 2
← 6
← 24
Why use recursion?
Choose recursion when:
- Problem has self-similar substructure (trees, graphs)
- Problem naturally defined recursively (factorial, Fibonacci)
- You need backtracking (maze solving, permutations)
- You’re implementing divide and conquer (merge sort, quick sort)
- Recursive solution is cleaner than iterative
Avoid recursion when:
- Recursion depth is very large (stack overflow risk)
- Iterative solution is simpler and more efficient
- You need optimal space complexity (recursion uses O(n) stack space)
- Performance is critical and recursion overhead matters
Basic recursive examples
1. Factorial
2. Fibonacci sequence
3. Sum of array
Tree recursion examples
Binary tree height
Backtracking with recursion
Generate all permutations
N-Queens problem
function solveNQueens(n) {
const result = [];
const board = Array(n).fill().map(() => Array(n).fill('.'));
function isSafe(row, col) {
// Check column
for (let i = 0; i < row; i++) {
if (board[i][col] === 'Q') return false;
}
// Check diagonal (top-left)
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] === 'Q') return false;
}
// Check diagonal (top-right)
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] === 'Q') return false;
}
return true;
}
function backtrack(row) {
// Base case: placed all queens
if (row === n) {
result.push(board.map(r => r.join('')));
return;
}
// Try placing queen in each column
for (let col = 0; col < n; col++) {
if (isSafe(row, col)) {
board[row][col] = 'Q'; // Place queen
backtrack(row + 1); // Recurse
board[row][col] = '.'; // Backtrack
}
}
}
backtrack(0);
return result;
}
console.log(solveNQueens(4));
// [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
Divide and conquer with recursion
Binary search
function binarySearch(arr, target, left = 0, right = arr.length - 1) {
// Base case: not found
if (left > right) return -1;
const mid = Math.floor((left + right) / 2);
// Base case: found
if (arr[mid] === target) return mid;
// Recursive case: search left or right half
if (arr[mid] > target) {
return binarySearch(arr, target, left, mid - 1);
} else {
return binarySearch(arr, target, mid + 1, right);
}
}
console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3
console.log(binarySearch([1, 3, 5, 7, 9, 11], 6)); // -1
Merge sort
function mergeSort(arr) {
// Base case: array of 0 or 1 element
if (arr.length <= 1) return arr;
// Divide
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
// Conquer (recursive calls)
const sortedLeft = mergeSort(left);
const sortedRight = mergeSort(right);
// Combine
return merge(sortedLeft, sortedRight);
}
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
console.log(mergeSort([5, 2, 8, 1, 9, 3])); // [1, 2, 3, 5, 8, 9]
Common recursion patterns
1. Linear recursion
Single recursive call (factorial, sum)
2. Binary recursion
Two recursive calls (Fibonacci, binary tree)
3. Tail recursion
Recursive call is last operation (optimizable)
// Not tail recursive
function factorial(n) {
if (n === 0) return 1;
return n * factorial(n - 1); // Multiplication after recursive call
}
// Tail recursive
function factorialTail(n, acc = 1) {
if (n === 0) return acc;
return factorialTail(n - 1, n * acc); // Recursive call is last
}
4. Mutual recursion
Functions calling each other
function isEven(n) {
if (n === 0) return true;
return isOdd(n - 1);
}
function isOdd(n) {
if (n === 0) return false;
return isEven(n - 1);
}
Complexity analysis
- Time: Depends on problem (often O(2^n) for naive, O(n) optimized)
- Space: O(n) for call stack depth in most cases
Common pitfalls
- Missing base case: Infinite recursion → stack overflow
- Wrong base case: Incorrect results or non-termination
- Not making progress: Each recursive call must move toward base case
- Inefficient recursion: Multiple redundant calculations (use memoization)
- Stack overflow: Very deep recursion exhausts call stack
- Modifying shared state: Be careful with global variables
When to use recursion
- Tree/graph traversal: Natural recursive structure
- Divide and conquer: Binary search, merge sort, quick sort
- Backtracking: N-Queens, Sudoku, permutations
- Mathematical: Factorial, Fibonacci, combinations
- Nested structures: Parsing, expression evaluation
Practice problems
- Power function (x^n) with O(log n) complexity
- Reverse a string recursively
- Check if string is palindrome (recursive)
- Tower of Hanoi
- Generate all subsets of a set
- Count ways to climb stairs (1 or 2 steps at a time)
- Decode ways (letter combinations)
- Word break problem
- Generate valid parentheses combinations
- Combination sum
- Sudoku solver
- Regular expression matching
- Letter combinations of phone number
- Flatten nested list
- Evaluate expression tree
Complexity summary
- Time: Varies greatly (O(n), O(n log n), O(2^n), etc.)
- Space: O(d) where d is max recursion depth (call stack)
Recursion is a powerful tool that simplifies complex problems with self-similar structure. Master recursion and you’ll be able to elegantly solve problems involving trees, graphs, backtracking, and divide-and-conquer algorithms.