Recursion
Fundamentals · Core
A function is recursive when it calls itself on a smaller version of the same problem. Trees, graphs, backtracking and dynamic programming are all recursion in some form, so it is worth getting comfortable first.
Anatomy
Every recursive function needs:
- A base case: an input so small the answer is immediate. Without it the function never stops.
- A recursive case: solve the problem by calling yourself on a smaller input and combining the result.
- Progress: each call must move toward the base case.
function factorial(n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // smaller problem
}The call stack
Each call waits on the stack until the call it made returns. factorial(3) runs as:
factorial(3) = 3 * factorial(2)
= 2 * factorial(1)
= 1then unwinds: 1, then 2, then 6. A very deep recursion overflows the stack (RangeError: Maximum call stack size exceeded, roughly 10,000 frames in JavaScript). JavaScript engines do not optimise tail calls, so turn deep recursion into a loop or an explicit stack.
Reading the cost
Write the recurrence, then solve it.
| Recurrence | Time | Example |
|---|---|---|
| T(n) = T(n-1) + O(1) | O(n) | factorial, list length |
| T(n) = T(n/2) + O(1) | O(log n) | binary search |
| T(n) = 2T(n/2) + O(n) | O(n log n) | merge sort |
| T(n) = 2T(n-1) + O(1) | O(2ⁿ) | all subsets, naive Fibonacci |
Space is the depth of the stack: O(n) for factorial, O(log n) for binary search.
Common shapes
- Linear: one call per step (sum of a list).
- Divide and conquer: split in halves, combine (merge sort).
- Tree recursion: one call per child (tree traversals, DFS).
- Backtracking: choose, recurse, undo.
- Overlapping calls: the same input is solved again and again. Cache results: see Memoization.
How to think about it
Do not trace every call. Assume the function already works for smaller inputs and ask only: given those answers, how do I build mine, and what is the base case?
Common mistakes
- Missing or wrong base case, giving infinite recursion.
- Not making progress (calling with the same input).
- Copying arrays on every call (
slice, spread) and silently making it O(n²). Pass indexes instead. - Recomputing overlapping subproblems (naive Fibonacci is O(2ⁿ)).
Code
export const sum = (arr, i = 0) => (i === arr.length ? 0 : arr[i] + sum(arr, i + 1));
export const power = (x, n) => { // O(log n): halve the exponent
if (n === 0) return 1;
const half = power(x, Math.floor(n / 2));
return n % 2 ? half * half * x : half * half;
};
export const reverse = s => (s.length <= 1 ? s : reverse(s.slice(1)) + s[0]);
export const flatten = arr => arr.reduce((out, x) => out.concat(Array.isArray(x) ? flatten(x) : x), []);
export const fibSlow = n => (n < 2 ? n : fibSlow(n - 1) + fibSlow(n - 2)); // O(2^n)
export const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
export const permutations = (items, path = []) => {
if (!items.length) return [path];
return items.flatMap((x, i) => permutations([...items.slice(0, i), ...items.slice(i + 1)], [...path, x]));
};Practice
Backtracking
- Binary Tree PathsEasy
- SubsetsMedium
- Subsets IIMedium
- PermutationsMedium
- Permutations IIMedium
- CombinationsMedium
- Combination SumMedium
- Combination Sum IIMedium
- Combination Sum IIIMedium
- Letter Combinations of a Phone NumberMedium
- Generate ParenthesesMedium
- Palindrome PartitioningMedium
- Word SearchMedium
- Restore IP AddressesMedium
- Letter Case PermutationMedium
- Partition to K Equal Sum SubsetsMedium
- N-QueensHard
- Sudoku SolverHard
- Word Break IIHard
- Expression Add OperatorsHard
Dynamic Programming
- Climbing StairsEasy
- Fibonacci NumberEasy
- Min Cost Climbing StairsEasy
- Pascal's TriangleEasy
- House RobberMedium
- House Robber IIMedium
- Coin ChangeMedium
- Coin Change IIMedium
- Longest Increasing SubsequenceMedium
- Longest Common SubsequenceMedium
- Word BreakMedium
- Unique PathsMedium
- Unique Paths IIMedium
- Minimum Path SumMedium
- Jump GameMedium
- Partition Equal Subset SumMedium
- Target SumMedium
- Decode WaysMedium
- Maximum Product SubarrayMedium
- Longest Palindromic SubstringMedium
- Palindromic SubstringsMedium
- Best Time to Buy and Sell Stock with CooldownMedium
- TriangleMedium
- Maximal SquareMedium
- Edit DistanceHard
- Regular Expression MatchingHard
- Burst BalloonsHard
- Distinct SubsequencesHard
- Longest Valid ParenthesesHard
- Best Time to Buy and Sell Stock IVHard
Divide & Conquer
- Majority ElementEasy
- Convert Sorted Array to Binary Search TreeEasy
- Pow(x, n)Medium
- Sort ListMedium
- Maximum SubarrayMedium
- Different Ways to Add ParenthesesMedium
- Construct Quad TreeMedium
- Kth Largest Element in an ArrayMedium
- Merge k Sorted ListsHard
- Reverse PairsHard
- The Skyline ProblemHard
- Median of Two Sorted ArraysHard
Spotted a mistake or something unclear? Report it on GitHub.