The idea: a problem that contains itself
Recursion is the programming version of a simple observation: some problems are built out of smaller copies of themselves. To sort a stack of papers, sort each half and merge them. To count the files inside a folder, count the files in each subfolder and add them up. A recursive function mirrors that structure — it handles one small step directly, then hands the rest of the work to another copy of itself running on a smaller input.
Every recursive function answers two questions. First: what is the smallest case I can solve immediately, without recursing? That is the base case. Second: how do I reduce a bigger case to a smaller one of the same kind, then combine the result? That is the recursive case. Get both right and the function is correct; forget the base case and it calls itself forever.
The mechanics: the call stack
When a function calls itself, the program does not discard the caller — it pauses it. The computer maintains a call stack: a stack of frames, one per active call, each holding that call's local variables and the exact line to return to. factorial(4) calls factorial(3), which calls factorial(2), and so on, so four frames pile up before any of them finishes.
The base case is what lets the stack unwind. factorial(1) returns 1 without recursing, so its frame pops; now factorial(2) can compute 2 × 1 and pop, and control flows back down until factorial(4) returns 24. This is why recursion costs memory: the maximum stack depth is extra space you pay even when the code looks compact. Exceed the limit — Python defaults to roughly 1000 frames — and you get a stack overflow.
Recursion on trees
Recursion shines on data that is itself recursive. A binary tree is either empty, or a node with a left subtree and a right subtree — and each subtree is just a smaller tree. That definition is a base case (empty) plus a recursive case (a node and two subtrees), so traversal code reads almost like the definition itself: handle the empty case, recurse left, visit the node, recurse right.
This is also where recursion beats loops for clarity. Traversing a tree with a loop means managing an explicit stack by hand, while the recursive version lets the call stack do that bookkeeping for you. Watching the calls expand down one branch and collapse back up — then repeat on the next branch — is the moment recursion usually clicks, which is why it helps to see it animate step by step rather than read it cold.
def factorial(n):
"""Return n! using recursion. Assumes n >= 0."""
# Base case: 0! and 1! are both 1 — stop recursing.
if n <= 1:
return 1
# Recursive case: n! = n * (n - 1)!
return n * factorial(n - 1)
def inorder(node, visit):
"""Visit a binary tree's values in sorted (in-order) order."""
if node is None: # Base case: empty subtree.
return
inorder(node.left, visit) # Recurse left,
visit(node.value) # process this node,
inorder(node.right, visit) # then recurse right.Depth is best/average O(log n) for a balanced tree but worst-case O(n) for a degenerate one. Naive recursion that re-solves overlapping subproblems (e.g. plain Fibonacci) can explode to O(2^n) unless memoized.
When to use it
- Traversing or building tree and graph structures, where each node has the same shape as the whole (DFS, tree height, folder walks).
- Divide-and-conquer algorithms that split a problem into smaller identical subproblems — merge sort, quicksort, binary search.
- Problems defined by a recurrence or by exploring choices with undo — factorial, permutations and subsets, and backtracking puzzles like N-Queens.
- When the recursive version is dramatically clearer than the iterative one, such as flattening arbitrarily nested data.
Watch out for
- Missing or unreachable base case, or a recursive call that doesn't shrink the input — this causes infinite recursion and a stack overflow.
- Re-solving the same subproblem repeatedly; naive Fibonacci is O(2^n). Add memoization or convert to dynamic programming when subproblems overlap.
- Ignoring stack-depth limits: deep linear recursion crashes on large inputs. Rewrite it iteratively (or with an explicit stack) when depth can grow unbounded.
Recursion is foundational interview material — expect it both as a direct topic and as the hidden engine behind harder problems. Interviewers use small prompts like factorial, reversing a linked list, or Fibonacci to check that you can name a clean base case and a correctly shrinking recursive case without off-by-one errors. It then reappears everywhere: tree and graph traversal (DFS), divide-and-conquer sorts, and every backtracking question (permutations, subsets, N-Queens) is recursion with undo. What they are really watching is whether you can state the recurrence, reason about its time and space including stack depth, and spot when naive recursion repeats work and should be memoized. Being able to trace the call stack out loud, and to convert a recursive solution to an iterative one on request, signals genuine understanding rather than a memorized template.