Recursion is when a function calls itself to solve a smaller version of the same problem. It can feel like magic at first — how does a function "pause" to wait for itself? — but once you understand the call stack underneath it, recursion becomes one of the most natural tools in a programmer's toolkit, especially for problems that are naturally defined in terms of themselves, like tree traversal or breaking a problem into smaller identical pieces.
Every recursive function needs two parts: a base case (the simplest version of the problem, answered directly, with no further recursion) and a recursive case (where the function calls itself with a smaller or simpler input, moving toward the base case). Skip the base case, or never actually get closer to it, and you get infinite recursion — which in practice crashes with a stack overflow / maximum recursion depth error.
The Call Stack Mental Model
Every time a function calls itself, the computer doesn't throw away the current call — it pauses it, remembers exactly where it was and what its local variables were, and starts a brand new call on top. This is the call stack: a stack of paused function calls, each waiting for the one above it to finish and return a value before it can continue.
def factorial(n):
# Base case: the simplest version of the problem
if n == 0:
return 1
# Recursive case: n! = n * (n-1)!
return n * factorial(n - 1)
print(factorial(4))
Output:
24
Tracing the Stack Step by Step
Here's exactly what happens when you call factorial(4). Each call stacks on top of the last until the base case is hit, then the whole stack "unwinds," with each paused call finally computing its multiplication and returning:
factorial(4)
-> 4 * factorial(3)
-> 3 * factorial(2)
-> 2 * factorial(1)
-> 1 * factorial(0)
-> returns 1 (base case hit)
-> returns 1 * 1 = 1
-> returns 2 * 1 = 2
-> returns 3 * 2 = 6
-> returns 4 * 6 = 24
Notice that factorial(4) can't finish until factorial(3) returns, which can't finish until factorial(2) returns, and so on, all the way down to the base case. Nothing actually gets multiplied until the base case is hit and the stack starts "unwinding" back up — that's why the result builds from the inside out.
Recursion Has a Real Cost
Each paused call takes up memory on the call stack, so a recursive function with depth n uses O(n) extra space, even if the work it does is simple. Very deep recursion (thousands of calls) can hit a language's recursion limit and crash — this is one reason an equivalent loop-based solution using an explicit stack is sometimes preferred for performance-sensitive code, even though the recursive version is often easier to read.
Recursion really shines on problems that break down into smaller copies of themselves — tree and graph traversal, divide-and-conquer algorithms, and backtracking. You can see recursion used to implement real algorithms in our Python factorial program, and it's the foundation for more advanced techniques covered later in this course.
