A Function That Calls Itself
Nothing stops a C function from calling itself. Its own name is in scope inside its body, so this is legal:
void countdown(int n) {
printf("%d\n", n);
countdown(n - 1); /* calls itself - but never stops! */
}
It is also broken. It prints forever, into negative numbers, until the program crashes. What it is missing is a base case: a condition under which the function returns without calling itself.
Every recursive function has exactly these two parts:
- A base case - the smallest input, answered directly, with no further call.
- A recursive case - solves the problem in terms of a strictly smaller version of itself.
"Strictly smaller" is the part people get wrong. countdown(n - 1) moves toward 0 on every call. countdown(n) would not, and neither would countdown(n / 2) if n could be 1 forever. Every path must shrink the problem, or the base case is never reached.
Factorial
The standard first example. n! is n × (n-1) × ... × 1, and 0! is defined as 1. That definition is already recursive: n! = n × (n-1)!.
Trace factorial(4) to see how the answer is assembled. The calls go down, and the multiplications happen on the way back up:
factorial(4) -> 4 * factorial(3)
factorial(3) -> 3 * factorial(2)
factorial(2) -> 2 * factorial(1)
factorial(1) -> 1 (base case)
factorial(2) = 2 * 1 = 2
factorial(3) = 3 * 2 = 6
factorial(4) = 4 * 6 = 24
Nothing is multiplied until the base case returns. Every pending call sits waiting, holding its own n, which is the point worth internalising: those waiting calls occupy memory.
Note the return type. int overflows around 13!, silently producing a wrong number - C does not check. unsigned long long gets you to 20! and no further, because 21! exceeds 64 bits. Recursion is not the limiting factor here; the type is.
The base case uses n <= 1 rather than n == 1 deliberately: factorial(0) should be 1, and <= handles it. With n == 1, calling factorial(0) would recurse to -1, -2, and never terminate - a good illustration of how an "obviously correct" base case can miss an input.
Fibonacci, and Why the Naive Version Is a Trap
Fibonacci is the other classic: each number is the sum of the two before it, starting from 0 and 1. The recursive definition writes itself.
Look at the call counts. fib(10) takes 177 calls; fib(35) takes nearly 30 million. Each step of 5 multiplies the work by about eleven.
The reason is visible in the call tree. fib(5) calls fib(4) and fib(3); fib(4) calls fib(3) again; and each of those recomputes fib(2) from scratch. Nothing is remembered, so the same subproblems are solved over and over, and the number of calls grows roughly like 1.6ⁿ. fib(50) this way would run for days; fib(100) would outlast the universe.
The loop version keeps the last two values and is linear:
fib(90) returns instantly. The lesson is not "recursion is slow" - it is that recursion with overlapping subproblems is slow unless you remember the answers. Store results in an array as you compute them (memoization) and the recursive version becomes linear too.
The Call Stack and Stack Overflow
Every function call needs somewhere to keep its parameters, its locals, and the address to return to. That storage is a stack frame, pushed when the call starts and popped when it returns. Recursion stacks frames one on top of another - factorial(1000) has a thousand frames live at once, each with its own n.
The stack is not large. A typical default is 1-8 MB, so a few tens of thousands of frames is the realistic limit, and much less if each frame holds a big local array. Exceed it and the program dies:
Segmentation fault (core dumped)
That is a stack overflow, and there are two ways to get one:
Infinite recursion - a missing or unreachable base case. This is a bug, and the crash is immediate:
int bad(int n) {
return bad(n - 1); /* no base case - crashes in a fraction of a second */
}
Correct but too deep - recursing once per element over a million-item list. The logic is right; the approach does not fit in the stack. Rewrite it as a loop, or restructure so the depth is logarithmic (recursing on halves, as binary search and merge sort do, gives a depth of about 20 for a million items).
Some compilers can turn tail recursion - where the recursive call is the very last thing the function does, with no pending work after it - into a loop, reusing one frame. countdown above is tail recursive; factorial is not, because the multiplication still has to happen after the call returns. But C does not require this optimisation, so it may or may not happen depending on the compiler and the flags. Never write C that only works because the optimiser eliminated a tail call.
Where Recursion Genuinely Wins
Every recursive function can be rewritten as a loop, and for simple counting the loop is plainly better. Recursion earns its keep when the data itself is recursive - when a structure contains smaller copies of itself.
Binary search is a clean example: search half, then half of that.
Two base cases here, which is normal: one for success and one for exhaustion. The depth is about log₂(n), so even a billion elements need only thirty frames.
Other places recursion is the natural fit: walking a tree or a linked list, directory traversal, parsing nested expressions, and divide-and-conquer sorts like quicksort and merge sort. In all of them the recursive code is shorter and clearer than the loop with an explicit stack that replaces it.
Recursion or a Loop?
Use a loop when the problem is linear - counting, summing, scanning
Use recursion when the data is nested - trees, nested structures, divide and conquer
Rewrite recursion if the depth can grow with the input size without bound
Never use recursion when subproblems overlap, unless you memoize
Two practical notes. Recursive calls cost a little more than a loop iteration - a frame to push and pop each time - so for hot, simple loops the iterative version wins on speed as well as memory. And debugging is different: a stack trace from deep recursion is hundreds of identical-looking frames, so print the parameter at entry (as the calls counter above does) when something is not terminating.
Writing a Recursive Function: A Checklist
- Find the base case first. What is the smallest input, and what is its answer? If you cannot name it, the function cannot be written.
- Assume the recursive call works. Do not trace it through mentally - trust
factorial(n - 1)to return(n-1)!and write the one step that turns it into the answer. - Check every path shrinks. Each recursive call must move toward the base case for every possible input, including 0 and negatives.
- Check the depth. Roughly how many frames deep will this go on real data? Thousands is fine; millions is not.
- Check for overlap. If the same subproblem is computed twice, you need memoization or a loop.
Frequently Asked Questions
What is recursion in C?
A function that calls itself to solve a smaller version of the same problem. Every recursive function needs two things: a base case that returns without recursing, and a recursive case that moves measurably closer to it. Without the base case the calls never stop and the program crashes with a stack overflow.
How do you write a factorial function in C?
int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); }. The base case handles 0 and 1, and each recursive call reduces n by one until it reaches it. Note that int overflows at 13! - use unsigned long long for larger values.
Why is recursive Fibonacci so slow in C?
Because fib(n) calls fib(n-1) and fib(n-2), which recompute the same subproblems over and over - the number of calls grows exponentially, so fib(50) would take years. Rewriting it as a loop that keeps the last two values makes it linear and instant.
What causes a stack overflow in C recursion?
Each call takes a frame of stack memory for its parameters and locals, and the stack is only a few megabytes. A missing or unreachable base case means infinite recursion and an immediate crash; even correct recursion that goes hundreds of thousands of levels deep can exhaust the stack.