LearnAbout

Recursion Basics

On this page
Quick reference
factorial(n)O(n)
naive fib(n)O(2โฟ)
sumArray(arr)O(n)
pow(x, n) โ€” fast powerO(log n)
The Idea

Recursion is asking a smaller version of yourself to solve a smaller version of the problem, then using that answer to build yours. To compute factorial(5), you don't need to know how to multiply five numbers together โ€” you just need to know 5 ร— factorial(4), and trust that some other copy of you will figure out factorial(4). That copy trusts another copy for factorial(3). This keeps shrinking until it hits a version so small it can answer immediately without asking anyone else โ€” the base case.

The call stack โ€” the mechanism that makes this work โ€” is a stack of IOUs. Calling factorial(5) doesn't get an answer yet; it pushes an IOU that says "I owe you 5 ร— [whatever factorial(4) turns out to be]" and waits. factorial(4) pushes its own IOU and waits on factorial(3). This keeps going until factorial(0) is reached, which pays out immediately: 1, no waiting. Then everyone below it pays back up the stack in order โ€” factorial(1) collects its answer and returns 1, factorial(2) collects that and returns 2, and so on until factorial(5) finally has its answer.

That word "stack" isn't a metaphor โ€” the call stack genuinely is a stack, last-in-first-out, the exact structure Phase 3 covers explicitly. The most recently called (and least resolved) function is always the one that returns next. You don't need to know that structure by name to write recursion, but it's why recursion and stacks are the same idea wearing two different hats.

Every recursive function needs two things: a base case (the smallest input, answered directly, no further recursive call) and a recursive case (the function calling a smaller version of itself and using that result). Miss either one and the "smaller and smaller" chain never bottoms out.
Build It
Shape of a recursive function
function solve(problem):
    if problem is trivially small:      // base case โ€” answer directly, no recursive call
        return direct answer
    smallerAnswer = solve(smaller problem)   // trust this call to work
    return combine(problem, smallerAnswer)    // use it to build this call's answer
factorial โ€” the textbook shape
function factorial(n):
    if n == 0:              // base case
        return 1
    return n * factorial(n - 1)   // recursive case: trust factorial(n-1), then multiply
naive Fibonacci โ€” the same shape, but it blows up
function fib(n):
    if n <= 1:                       // base case
        return n
    return fib(n - 1) + fib(n - 2)   // TWO recursive calls, not one
factorial makes one recursive call per level, so the work grows linearly with n. fib makes TWO recursive calls per level, and each of those makes two more โ€” the number of calls roughly doubles every level, giving O(2โฟ) total calls. fib(5) recomputes fib(3) and fib(2) multiple times over; nothing here remembers a previous answer.
sum of an array โ€” recursing on 'the rest'
function sumArray(arr, i = 0):
    if i == len(arr):          // base case: walked off the end, nothing left to add
        return 0
    return arr[i] + sumArray(arr, i + 1)   // this element, plus the sum of everything after it
Pow(x, n) โ€” divide-and-conquer, a smarter recursion
function pow(x, n):
    if n == 0:                     // base case
        return 1
    half = pow(x, n // 2)          // solve HALF the problem, once
    if n % 2 == 0:
        return half * half          // even exponent: square the half
    else:
        return half * half * x      // odd exponent: one extra factor of x
Naive recursion (x * pow(x, n-1)) shrinks n by 1 each call โ€” O(n) calls. Here, each call shrinks n by HALF, because it reuses `half` instead of recursing twice โ€” that halving is exactly the pattern from Big-O's O(log n) case.
Know It

Time counts total calls made. Space counts the deepest the call stack ever gets โ€” the number of IOUs waiting at once โ€” which is easy to forget since it looks like "just a function call," not a data structure, but every pending call sits in memory until it returns.

OperationTimeSpaceWhy
factorial(n)O(n)O(n)n calls deep before the base case, and every one of those n calls stays on the stack (waiting on the next) until the base case returns โ€” that's O(n) stack frames alive at once, even though no array or list was ever created.
naive fib(n)O(2โฟ)O(n)Total calls roughly double each level down โ€” O(2โฟ) calls overall. But at any single moment, only one chain of calls is active at a time (the stack unwinds before the next branch starts), so the deepest the stack ever gets is still just O(n).
sumArray(arr)O(n)O(n)One call per element, and all n calls are waiting on the stack simultaneously before the base case returns and they start paying back up.
pow(x, n) โ€” fast powerO(log n)O(log n)n is halved every call, so it takes logโ‚‚(n) calls to reach the base case โ€” and that's also how deep the stack gets, since each call waits on the next.
Break It

Missing or wrong base case

no condition ever stops the recursion
HEADโ†’call(n)โ€ขโ†’call(n)โ€ขโ†’call(n)โ€ขโ†ฉ back to call(n)

A recursive function with no base case (or a base case that's never reached โ€” e.g. counting up instead of down) keeps pushing IOUs forever until the stack runs out of memory: a stack overflow. It's the exact same shape of bug as a linked list node whose `next` never reaches NULL โ€” a chain with no terminator, walked until something breaks.

Empty input as the trivial base case

the input is already empty

Many recursive functions over collections (sum an array, reverse a string) need "nothing left to process" as a base case โ€” index equals length, or the array is []. Skipping this and assuming there's always at least one element is a common off-by-one source: the function crashes or under-counts on the smallest legal input.

Single-element input as the smallest real case

the input has exactly one element

Distinct from the empty case โ€” a single element is often the smallest input where the 'interesting' logic actually runs once. Testing your recursion against a one-element input catches bugs that an empty-input test can't, like an off-by-one in how the recursive call narrows the problem.

Recomputing the same subproblem exponentially many times

recursive calls overlap, like naive Fibonacci

fib(5) calls fib(4) and fib(3); fib(4) calls fib(3) and fib(2) โ€” fib(3) just got computed twice, and it only gets worse deeper down. The function has no memory of what it already solved, so identical work repeats over and over, which is exactly why naive Fibonacci is O(2โฟ) instead of O(n). The fix (caching answers you've already computed) has a name โ€” memoization โ€” but that's a tool for a later phase; for now, just recognize the symptom: overlapping recursive calls solving the same input more than once.

Use It
Fibonacci Number
The textbook exponential-blowup example: fib(n) = fib(n-1) + fib(n-2), base cases fib(0)=0 and fib(1)=1. Write it naively first and feel the O(2โฟ) slowdown.
Easy
Climbing Stairs
Ways to reach step n = ways to reach (n-1) + ways to reach (n-2) โ€” the exact same recurrence shape as Fibonacci, just relabeled.
Easy
Reverse String
Swap the first and last characters, then recurse on everything strictly between them โ€” the base case is zero or one characters left.
Easy
Pow(x, n)
The fast-power divide-and-conquer pattern from Build It, verbatim โ€” watch out for negative n (invert x and negate n first).
Medium
Merge Two Sorted Lists
Each call compares the two current heads, picks the smaller, and recurses on 'merge the rest.' (Phase 2's Linked Lists topic will revisit this with an iterative solution.)
Easy
Remove Duplicates from Sorted List
Process head, recurse on head.next, then decide whether to skip head based on what comes back. (Phase 2's Linked Lists topic will revisit this with an iterative solution.)
Easy
Add Digits
Peel off one digit sum per call and recurse on the rest โ€” sum the digits, then recurse on that sum, until only one digit remains.
Easy
Reverse Linked List
In three lines recursively: recurse to the end first, then flip each `next` pointer as the stack unwinds back up. (Phase 2's Linked Lists topic will revisit this with an iterative solution.)
Easy
Factorial Trailing Zeroes
Trailing zeroes come from factors of 10 = 2 ร— 5, and factors of 2 always outnumber factors of 5 in n! โ€” so just count factors of 5, recursively: n/5 + count(n/5).
Medium
Excel Sheet Column Title
The reverse of Excel Sheet Column Number, done recursively: peel off the last base-26 digit (careful โ€” it's 1-indexed, A=1), recurse on what remains.
Medium