TechLead
Intermediate
22 min
Full Guide

Recursion

Recursive thinking, base cases, and recursive problem solving

Recursion is a technique where a function solves a problem by calling itself on a smaller version of the same problem. It is the natural fit for problems with a self-similar structure: trees, graphs, divide-and-conquer, and combinatorics.

The Two Required Parts

Every correct recursive function has a base case that stops the recursion and a recursive case that makes progress toward the base case. Missing either causes infinite recursion or a stack overflow.

function factorial(n) {
  if (n <= 1) return 1;         // base case
  return n * factorial(n - 1); // recursive case — n decreases each call
}

factorial(5); // 5 * 4 * 3 * 2 * 1 = 120

Thinking Recursively

The mental model: assume the function already works for smaller inputs and express the answer in terms of those smaller results. You do not need to trace every call stack level.

// Sum all numbers in a nested array — treat each element as a sub-problem
function deepSum(arr) {
  return arr.reduce((total, item) => {
    return total + (Array.isArray(item) ? deepSum(item) : item);
  }, 0);
}

deepSum([1, [2, [3, 4], 5]]); // 15

The Call Stack

Each recursive call adds a frame to the call stack. JavaScript's default stack depth is roughly 10,000–15,000 frames. Deep recursion on large inputs can exceed this limit.

// Stack overflow risk on large n:
function sum(n) {
  if (n === 0) return 0;
  return n + sum(n - 1); // 100,000 calls = stack overflow
}

// Iterative equivalent — no stack risk:
function sumIterative(n) {
  let total = 0;
  for (let i = 1; i <= n; i++) total += i;
  return total;
}

Memoization — Caching Repeated Work

Naive recursion recomputes the same subproblems. Memoization stores results and returns the cached value on repeated calls, turning exponential time into linear.

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;
}

// Without memoization: O(2^n)
// With memoization:    O(n)

Tree Traversal — A Natural Recursive Problem

function treeSum(node) {
  if (node === null) return 0;
  return node.val + treeSum(node.left) + treeSum(node.right);
}

function maxDepth(node) {
  if (node === null) return 0;
  return 1 + Math.max(maxDepth(node.left), maxDepth(node.right));
}

Tail Recursion

A recursive call is in tail position when it is the very last operation — no work remains after the call returns. Some engines (not V8) optimise this into a loop, eliminating extra stack frames.

// Tail-recursive factorial using an accumulator
function factTail(n, acc = 1) {
  if (n <= 1) return acc;
  return factTail(n - 1, n * acc); // last operation is the recursive call
}

When to Prefer Iteration

  • When input can be arbitrarily large (stack overflow risk)
  • When every level only processes one item (simple loops are faster)
  • When the language/engine does not optimise tail calls

Use recursion when it makes the structure of the problem clearer — trees, graphs, divide-and-conquer — and iteration when it is equally clear but safer.

Continue Learning