ANALYSIS & FOUNDATIONS / ALGORITHM BRIEF

Recursion fundamentals

Recursion is when a function solves a problem by calling itself on a smaller version of the same problem.

BeginnerPhase 01 / Topic 3 of 7Mental modelComplexityEdge cases
01

Overview

Recursion is when a function solves a problem by calling itself on a smaller version of the same problem. Every recursive function has two parts: a base case that returns an answer directly, and a recursive case that reduces the problem and trusts the recursive call to solve the smaller piece.

Recursion is the foundation for tree and graph traversal, backtracking, divide and conquer, and dynamic programming. The key mental shift is to stop tracing every call and instead trust the function's definition: if sum(arr, i) returns the sum from index i onward, then sum(arr, 0) = arr[0] + sum(arr, 1).

Russian nesting dolls

To count the dolls, open the outer one and ask the same question about the doll inside: 1 + count(inner). When you reach the smallest solid doll (the base case), you answer 1 and all the waiting answers add up on the way back out.

02

When to use it

  • The data is recursive: trees, nested lists, file systems, JSON.
  • The problem is naturally defined in terms of smaller versions of itself (factorial, Fibonacci, power).
  • You need to explore choices: subsets, permutations, paths (backtracking).
  • You can split the input into independent halves (merge sort, quicksort).
03

Problem patterns it solves

Linear recursion (one call)

Recognize it when: process one element, recurse on the rest: reverse, sum, count, check palindrome.

  • 344. Reverse String
  • 206. Reverse Linked List (recursive)
  • 234. Palindrome Linked List
Tree recursion (two or more calls)

Recognize it when: answer depends on left and right subproblems.

  • 104. Maximum Depth of Binary Tree
  • 226. Invert Binary Tree
  • 509. Fibonacci Number
Take / not take

Recognize it when: for each element choose to include it or skip it: subsets, subsequences, knapsack.

  • 78. Subsets
  • 39. Combination Sum
  • 416. Partition Equal Subset Sum
Divide and conquer

Recognize it when: split into halves, solve each, combine.

  • 50. Pow(x, n)
  • 912. Sort an Array
  • 241. Different Ways to Add Parentheses
Recursion on generated strings

Recognize it when: build answers character by character with constraints.

  • 22. Generate Parentheses
  • 17. Letter Combinations of a Phone Number
  • 1190. Reverse Substrings Between Each Pair of Parentheses
04

Where it is used in real software

File systems

Computing a folder's total size, or deleting a directory tree (rm -r), recursively processes each subfolder.

Parsers and compilers

Recursive descent parsers handle nested expressions like (1 + (2 * 3)). JSON.parse and most expression evaluators work this way.

UI component trees

React renders a component tree recursively; DOM traversal APIs and tree-shaped menus are processed with recursion.

Fractals and graphics

Fractal rendering, quadtrees for collision detection, and scene graphs in game engines are recursive structures.

05

Key terms

Base case
The smallest input answered directly without another call. Without it, recursion never stops.
Recursive case
The part that reduces the problem and calls the function again.
Call stack
The runtime stack of active function calls. Each call waits for its child to return.
Stack overflow
Error when recursion depth exceeds the stack limit (often around 10,000 frames in JS).
Leap of faith
Assume the recursive call correctly solves the smaller problem; only verify how you use its result.
06

How to write any recursive function

  1. 1
    Define the function in one sentence

    Example: sum(arr, i) returns the sum of arr[i..end]. Be precise about inputs and what is returned.

  2. 2
    Find the base case

    What is the smallest input? When i === arr.length, the remaining sum is 0.

  3. 3
    Make the problem smaller

    Reduce toward the base case: i + 1, n - 1, n / 2, node.left.

  4. 4
    Combine using the definition

    Trust sum(arr, i + 1) and use it: arr[i] + sum(arr, i + 1).

  5. 5
    Check progress and termination

    Every call must move closer to the base case, or it will loop forever.

The call stack for factorial(4)
Step 1 / 6
fact(4)
0
1
2
3

STEP 1Call fact(4). It needs fact(3) before it can return, so it waits on the stack.

07

Trace of fibonacci(4) without memoization

fib(n) = fib(n - 1) + fib(n - 2), fib(0) = 0, fib(1) = 1

Step 1 / 6
CallDepthCalls it makesReturns
fib(4)0fib(3), fib(2)3
fib(3)1fib(2), fib(1)2
fib(2)2fib(1), fib(0)1
fib(1), fib(0)3base cases1, 0
fib(1)2base case1
fib(2) again1fib(1), fib(0) again1

NOWCall: fib(4) | Depth: 0 | Calls it makes: fib(3), fib(2) | Returns: 3

fib(2) is computed twice already at n = 4, and the duplication doubles with each level, giving O(2^n) calls. Caching results (memoization) reduces this to O(n) and is the bridge to dynamic programming.

08

Implementation

// Linear recursionfunction factorial(n) {  if (n <= 1) return 1;          // base case  return n * factorial(n - 1);   // smaller problem} // Recursion on an index instead of slicing (slice would copy: O(n^2))function isPalindrome(s, left = 0, right = s.length - 1) {  if (left >= right) return true;  if (s[left] !== s[right]) return false;  return isPalindrome(s, left + 1, right - 1);} // Divide in half: O(log n) callsfunction power(x, n) {  if (n === 0) return 1;  if (n < 0) return 1 / power(x, -n);  const half = power(x, Math.floor(n / 2));  return n % 2 === 0 ? half * half : half * half * x;} // Tree recursion with memoization: O(2^n) becomes O(n)function fib(n, memo = new Map()) {  if (n <= 1) return n;  if (memo.has(n)) return memo.get(n);  const value = fib(n - 1, memo) + fib(n - 2, memo);  memo.set(n, value);  return value;} // Generate strings with constraintsfunction generateParenthesis(n) {  const result = [];  function build(current, open, close) {    if (current.length === 2 * n) {      result.push(current);      return;    }    if (open < n) build(current + "(", open + 1, close);    if (close < open) build(current + ")", open, close + 1);  }  build("", 0, 0);  return result;}
09

Complexity and performance

Linear recursionO(n) time

One call per element; O(n) stack space.

Halving recursionO(log n)

Depth log n; O(log n) stack space.

Two branches, depth nO(2^n)

Like naive Fibonacci; memoize if states repeat.

Stack spaceO(depth)

Not O(total calls): only one path is active at a time.

10

Trade-offs

Readability vs stack limits

Recursive tree code is short and clear, but a skewed tree with 100,000 nodes can overflow the stack. Convert to an explicit stack if depth can be large.

Copying inputs

Passing arr.slice(1) or s.substring(1) creates a copy each call, turning O(n) into O(n^2). Pass indexes instead.

Repeated work

If the same arguments appear multiple times in the recursion tree, add memoization.

11

Variants and related techniques

Head vs tail recursion

Head recursion does work after the recursive call returns (printing in reverse); tail recursion does work before (printing in order).

Helper functions

A public function often wraps a private helper that carries extra state such as an index, a path, or a result list.

Mutual recursion

Two functions call each other, as in isEven/isOdd or grammar rules in parsers.

12

Common mistakes

  • Missing or wrong base case.

    Fix: Test the smallest inputs first: empty array, n = 0, null node.

  • Not returning the recursive result.

    Fix: Write return f(n - 1) rather than just calling f(n - 1).

  • Sharing a mutable list without undoing changes.

    Fix: Push before the call and pop after it (backtracking), or pass a copy when recording a result.

  • Tracing every call in your head.

    Fix: Trust the definition for the smaller problem and verify only the current level.

13

Interview questions

What happens in memory during recursion?

Each call pushes a frame with its parameters and local variables onto the call stack. It stays there until the call returns, so memory is proportional to the maximum depth.

Can every recursive function be written iteratively?

Yes. Any recursion can be simulated with an explicit stack. Tail-recursive functions convert to a simple loop.

How do you decide recursion is the right tool?

When the problem breaks into smaller identical subproblems, or the data is hierarchical. If the recursion revisits the same states, it becomes dynamic programming.

14

Practice problems

ProblemDifficultyWhat it trains
509. Fibonacci NumberEasyNaive recursion, then memoize.
344. Reverse StringEasyTwo-index recursion.
206. Reverse Linked ListEasyRecursive pointer rewiring.
50. Pow(x, n)MediumHalving recursion and negative exponents.
22. Generate ParenthesesMediumRecursion with constraints.
1823. Find the Winner of the Circular GameMediumJosephus recurrence: f(n) = (f(n - 1) + k) % n.