π JavaScript Recursion Basics
Parent: Recursion & Backtracking Index
What is Recursion?
Recursion is when a function calls itself to solve a smaller version of the same problem. Every recursive solution has two parts:
- Base Case β The condition that stops the recursion
- Recursive Case β The function calls itself with a smaller input
function countdown(n) {
// Base case β stop here
if (n <= 0) {
console.log("Done!");
return;
}
console.log(n);
// Recursive case β smaller input (n-1)
countdown(n - 1);
}
countdown(5);
// Output: 5, 4, 3, 2, 1, Done!
How the Call Stack Works
Every function call gets its own stack frame. Frames pile up until the base case is hit, then they unwind:
countdown(3) called
ββ countdown(2) called
ββ countdown(1) called
ββ countdown(0) called β "Done!" β returns
countdown(1) returns
countdown(2) returns
countdown(3) returns
Maximum call stack exceeded is JavaScript's stack overflow. Default limit is ~10,000β15,000 frames. Always ensure the base case is reachable.
Q1. Factorial
Problem: Compute n! = n Γ (n-1) Γ ... Γ 1
π§ How to Approach This
Step 1 β Recognize the pattern:
n! = n Γ (n-1)! β reduces to a smaller version of itself.
Step 2 β Base case: 0! = 1, 1! = 1
Step 3 β Recursive case: factorial(n) = n Γ factorial(n-1)
Step 4 β Edge cases:
- n=0: return 1 (0! defined as 1)
function factorial(n) {
// Base case
if (n <= 1) return 1;
// Recursive case
return n * factorial(n - 1);
}
// Call stack for factorial(4):
// 4 * factorial(3)
// 3 * factorial(2)
// 2 * factorial(1) = 1 β base case
// = 2
// = 6
// = 24
console.log(factorial(5)); // 120
console.log(factorial(0)); // 1
console.log(factorial(10)); // 3628800
Time: O(n) | Space: O(n) β n frames on call stack
Q2. Fibonacci Number
Problem: Return the nth Fibonacci number. F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
π§ How to Approach This
Step 1 β Naive approach:
Direct recursion β O(2βΏ) β recalculates the same values millions of times.
Step 2 β Memoization (Top-down DP):
Store computed values in a Map. fib(30) computed only once.
Step 3 β Bottom-up (best for space):
Build from F(0), F(1) up to F(n) iteratively.
Step 4 β Edge cases:
- n=0 β 0, n=1 β 1
// ββ Naive β O(2βΏ) β DO NOT USE for large n ββ
function fibNaive(n) {
if (n <= 1) return n;
return fibNaive(n - 1) + fibNaive(n - 2);
}
// ββ Memoized β O(n) time, O(n) space ββ
function fib(n, memo = new Map()) {
if (n <= 1) return n;
if (memo.has(n)) return memo.get(n);
const result = fib(n - 1, memo) + fib(n - 2, memo);
memo.set(n, result);
return result;
}
// ββ Bottom-up DP β O(n) time, O(1) space (best) ββ
function fibDP(n) {
if (n <= 1) return n;
let a = 0, b = 1;
for (let i = 2; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
console.log(fib(10)); // 55
console.log(fib(40)); // 102334155
console.log(fibDP(50)); // 12586269025
Time: O(n) memoized | Space: O(n)
Q3. Sum of 1 to N
Problem: Compute 1 + 2 + ... + n using recursion.
π§ How to Approach This
Step 1: sum(n) = n + sum(n-1). Base case: sum(0) = 0
function sumN(n) {
if (n <= 0) return 0; // Base case
return n + sumN(n - 1); // Recursive case
}
// O(1) math formula (best!)
const sumFormula = n => (n * (n + 1)) / 2;
console.log(sumN(10)); // 55
console.log(sumFormula(100)); // 5050
Time: O(n) | Space: O(n)
Q4. Power (Fast Exponentiation)
Problem: Compute x^n. Handle negative exponents. O(log n).
π§ How to Approach This
Step 1 β Key insight: x^n = (x^(n/2))Β² β cuts n in half each call.
Step 2 β Even n: return half * half
Odd n: return x half half
function myPow(x, n) {
if (n === 0) return 1;
if (n < 0) return 1 / myPow(x, -n);
const half = myPow(x, Math.floor(n / 2));
return n % 2 === 0
? half * half // Even
: x * half * half; // Odd
}
console.log(myPow(2, 10)); // 1024
console.log(myPow(2, -2)); // 0.25
console.log(myPow(3, 5)); // 243
Time: O(log n) | Space: O(log n)
Q5. Reverse a String Using Recursion
Problem: Reverse a string using recursion.
π§ How to Approach This
Step 1: reverseStr("hello") = reverseStr("ello") + "h"
function reverseStr(s) {
if (s.length <= 1) return s; // Base case
return reverseStr(s.slice(1)) + s[0]; // Recursive case
}
// In-place with two pointers (more efficient)
function reverseInPlace(chars, left = 0, right = chars.length - 1) {
if (left >= right) return;
[chars[left], chars[right]] = [chars[right], chars[left]];
reverseInPlace(chars, left + 1, right - 1);
}
console.log(reverseStr("hello")); // "olleh"
console.log(reverseStr("recursion")); // "noisrucer"
Time: O(n) | Space: O(n)
Q6. Check Palindrome (Recursive)
Problem: Check if a string is a palindrome using recursion.
π§ How to Approach This
Step 1: First char == last char? Recurse on inner portion.
function isPalindrome(s, left = 0, right = s.length - 1) {
if (left >= right) return true; // Base case: pointers met
if (s[left] !== s[right]) return false; // Mismatch
return isPalindrome(s, left + 1, right - 1);
}
console.log(isPalindrome("racecar")); // true
console.log(isPalindrome("hello")); // false
console.log(isPalindrome("abba")); // true
Time: O(n) | Space: O(n)
Q7. Sum of Digits
Problem: Sum the digits of a number recursively.
π§ How to Approach This
Step 1: Peel last digit: sumDigits(1234) = 4 + sumDigits(123)
function sumDigits(n) {
n = Math.abs(n);
if (n < 10) return n;
return (n % 10) + sumDigits(Math.floor(n / 10));
}
console.log(sumDigits(1234)); // 10
console.log(sumDigits(9999)); // 36
Time: O(log n) | Space: O(log n)
Recursion vs Iteration
// Sum 1..n β Three approaches
// ITERATIVE β O(n) time, O(1) space
function sumIterative(n) {
let sum = 0;
for (let i = 1; i <= n; i++) sum += i;
return sum;
}
// RECURSIVE β O(n) time, O(n) space
function sumRecursive(n) {
if (n <= 0) return 0;
return n + sumRecursive(n - 1);
}
// FORMULA β O(1) time, O(1) space (best!)
const sumFormula = n => (n * (n + 1)) / 2;
Common Mistakes in JS
| Mistake | Example | Fix |
|---|---|---|
| Missing base case | Hits "Maximum call stack exceeded" | Always define base case first |
| Mutating array without copying | push/pop affects all frames | Pass [...arr] or use index |
Forgetting new Map() default | Memo shared across calls | Use memo = new Map() or closure |
return missing in recursive branch | Returns undefined | Ensure all paths return a value |