π JavaScript Recursion Patterns
Parent: Recursion & Backtracking Index
Overview
| Pattern | Description | Examples |
|---|---|---|
| Linear Recursion | One recursive call per step | Factorial, Sum |
| Tree Recursion | Two+ recursive calls | Fibonacci, Tower of Hanoi |
| Divide & Conquer | Split in half, recurse each half | Merge Sort, Binary Search |
| Tail Recursion | Recursive call is last operation | Optimized by some engines |
| Mutual Recursion | Two functions call each other | isEven/isOdd |
Pattern 1 β Tower of Hanoi
Problem: Move n disks from rod A to rod C using B as helper. Larger disk can never go on smaller.
π§ How to Approach This
Step 1 β Recognize the pattern:
Move top n-1 to helper β move disk n to dest β move n-1 from helper to dest.
Step 2 β Algorithm:
1. Base case: n=1 β print move
2. hanoi(n-1, from, via, to)
3. Print "Move disk n: from β to"
4. hanoi(n-1, via, to, from)
function hanoi(n, from, to, via) {
if (n === 1) {
console.log(`Move disk 1: ${from} β ${to}`);
return;
}
hanoi(n - 1, from, via, to); // Move top n-1 to helper
console.log(`Move disk ${n}: ${from} β ${to}`);
hanoi(n - 1, via, to, from); // Move n-1 from helper to dest
}
hanoi(3, 'A', 'C', 'B');
// Move disk 1: A β C
// Move disk 2: A β B
// Move disk 1: C β B
// Move disk 3: A β C
// ...
Time: O(2βΏ) β 2βΏ-1 total moves | Space: O(n)
Pattern 2 β Generate All Subsets
Problem: Return all possible subsets (power set) of an array.
π§ How to Approach This
Step 1 β Binary decision tree:
Each element β include OR exclude β 2βΏ subsets total.
Step 2 β Algorithm:
1. At each index: include element, recurse, pop (backtrack), recurse without
2. Base case: index == array length β push copy to result
function subsets(nums) {
const result = [];
const current = [];
function generate(idx) {
// Base case: processed all elements
if (idx === nums.length) {
result.push([...current]); // push a copy
return;
}
// Choice 1: INCLUDE nums[idx]
current.push(nums[idx]);
generate(idx + 1);
// Choice 2: EXCLUDE nums[idx]
current.pop();
generate(idx + 1);
}
generate(0);
return result;
}
console.log(subsets([1, 2, 3]));
// [[], [3], [2], [2,3], [1], [1,3], [1,2], [1,2,3]] β 8 subsets
Time: O(2βΏ) | Space: O(n)
Pattern 3 β Generate All Permutations
Problem: Return all permutations of an array.
π§ How to Approach This
Step 1 β Swap approach:
Fix one element at position
startby swapping with each position j >= start. Recurse. Swap back.Step 2 β Algorithm:
1. Base case: start == length β push copy
2. For j from start to end: swap(start, j), recurse(start+1), swap back
function permutations(nums) {
const result = [];
function permute(start) {
if (start === nums.length) {
result.push([...nums]); // copy current arrangement
return;
}
for (let j = start; j < nums.length; j++) {
// Choose: fix nums[j] at position start
[nums[start], nums[j]] = [nums[j], nums[start]];
// Explore
permute(start + 1);
// Un-choose (restore)
[nums[start], nums[j]] = [nums[j], nums[start]];
}
}
permute(0);
return result;
}
console.log(permutations([1, 2, 3]));
// [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]]
Time: O(n Γ n!) | Space: O(n)
Pattern 4 β Combination Sum
Problem: Find all combinations that sum to target. Each number may be used unlimited times.
π§ How to Approach This
Step 1 β Unlimited pick backtracking:
At each step: use current candidate again (same index) OR skip to next. Sort for early pruning.
Step 2 β Algorithm:
1. Base: target == 0 β found combination
2. Base: target < 0 or index out of bounds β prune
3. For i from start: if candidates[i] > target β break (sorted)
- push, recurse(i, target-candidates[i]), pop
function combinationSum(candidates, target) {
candidates.sort((a, b) => a - b);
const result = [];
const current = [];
function backtrack(start, remaining) {
if (remaining === 0) { result.push([...current]); return; }
for (let i = start; i < candidates.length; i++) {
if (candidates[i] > remaining) break; // sorted β prune
current.push(candidates[i]);
backtrack(i, remaining - candidates[i]); // same i β reuse
current.pop();
}
}
backtrack(0, target);
return result;
}
console.log(combinationSum([2, 3, 6, 7], 7)); // [[2,2,3],[7]]
console.log(combinationSum([2, 3, 5], 8)); // [[2,2,2,2],[2,3,3],[3,5]]
Time: O(n^(T/M)) | Space: O(T/M)
Pattern 5 β Binary Search (Recursive)
Problem: Implement binary search recursively.
π§ How to Approach This
Step 1: Divide search space in half each call.
function binarySearch(arr, target, left = 0, right = arr.length - 1) {
if (left > right) return -1; // Base case: not found
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (target < arr[mid]) return binarySearch(arr, target, left, mid - 1);
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
Time: O(log n) | Space: O(log n)
Pattern 6 β Merge Sort (Recursive)
Problem: Sort using merge sort (divide and conquer).
π§ How to Approach This
Step 1 β Divide: Split into halves recursively until single elements.
Step 2 β Conquer: Merge two sorted halves.
function mergeSort(arr) {
if (arr.length <= 1) return arr; // Base case
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
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, ...left.slice(i), ...right.slice(j)];
}
console.log(mergeSort([38, 27, 43, 3, 9, 82, 10]));
// [3, 9, 10, 27, 38, 43, 82]
Time: O(n log n) | Space: O(n)
Pattern Summary
| Pattern | Recursive Calls | Time | Use case |
|---|---|---|---|
| Linear | 1 | O(n) | Factorial, sum, reverse |
| Binary (D&C) | 2 β equal halves | O(n log n) | Merge sort, binary search |
| Tree/Exponential | 2+ decreasing | O(2βΏ) or O(n!) | Subsets, permutations |
| Backtracking | Varies | O(2βΏ) to O(n!) | Maze, N-Queens, Sudoku |