C Notes
Master recursion in C: base cases, recursive cases, stack frames, factorial, Fibonacci, tower of Hanoi, and more. Understand when recursion beats iteration and common pitfalls.
Recursion is when a function calls itself to solve a smaller version of the same problem. It's one of those concepts that feels magical when it clicks — and deeply confusing before it does. Recursion is the natural approach for problems that have a self-similar structure: trees, mathematical sequences, divide-and-conquer algorithms, and anything that can be defined in terms of smaller instances of itself.
How Recursion Works
Every recursive function needs two things:
- Base case — A condition that stops the recursion (prevents infinite calls)
- Recursive case — The function calls itself with a "smaller" problem
#include <stdio.h>
void countdown(int n) {
// Base case: stop when n reaches 0
if (n == 0) {
printf("Blast off!\n");
return;
}
// Recursive case: print n, then countdown from n-1
printf("%d... ", n);
countdown(n - 1); // Function calls itself
}
int main() {
countdown(5);
return 0;
}5... 4... 3... 2... 1... Blast off!
Classic Example: Factorial
The mathematical definition of factorial is inherently recursive:
0! = 1(base case)n! = n × (n-1)!(recursive case)
0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 8! = 40320 9! = 362880 10! = 3628800
How the Call Stack Unfolds
For factorial(4):
Fibonacci Sequence
Fibonacci sequence: F(0) = 0 F(1) = 1 F(2) = 1 F(3) = 2 F(4) = 3 F(5) = 5 F(6) = 8 F(7) = 13 F(8) = 21 F(9) = 34 F(10) = 55 F(11) = 89 F(12) = 144
Warning: This naive recursive Fibonacci is extremely inefficient — O(2^n) time complexity. For large n, use iteration or memoization.
Power Function
#include <stdio.h>
// Simple recursion: O(n)
double power(double base, int exp) {
if (exp == 0) return 1;
if (exp < 0) return 1.0 / power(base, -exp);
return base * power(base, exp - 1);
}
// Efficient recursion: O(log n) using divide-and-conquer
double fastPower(double base, int exp) {
if (exp == 0) return 1;
if (exp < 0) return 1.0 / fastPower(base, -exp);
double half = fastPower(base, exp / 2);
if (exp % 2 == 0)
return half * half;
else
return base * half * half;
}
int main() {
printf("2^10 = %.0f\n", power(2, 10));
printf("3^5 = %.0f\n", power(3, 5));
printf("2^-3 = %f\n", power(2, -3));
printf("Fast: 2^20 = %.0f\n", fastPower(2, 20));
return 0;
}2^10 = 1024 3^5 = 243 2^-3 = 0.125000 Fast: 2^20 = 1048576
Sum of Digits
#include <stdio.h>
int sumOfDigits(int n) {
if (n < 0) n = -n; // Handle negative
if (n < 10) return n; // Base case: single digit
return (n % 10) + sumOfDigits(n / 10);
}
int countDigits(int n) {
if (n < 0) n = -n;
if (n < 10) return 1;
return 1 + countDigits(n / 10);
}
int main() {
printf("Sum of digits of 12345: %d\n", sumOfDigits(12345));
printf("Sum of digits of 999: %d\n", sumOfDigits(999));
printf("Digits in 12345: %d\n", countDigits(12345));
printf("Digits in 7: %d\n", countDigits(7));
return 0;
}Sum of digits of 12345: 15 Sum of digits of 999: 27 Digits in 12345: 5 Digits in 7: 1
Tower of Hanoi
The classic recursion problem — move n disks from source to destination using an auxiliary peg:
Tower of Hanoi with 3 disks: Move disk 1 from A to C Move disk 2 from A to B Move disk 1 from C to B Move disk 3 from A to C Move disk 1 from B to A Move disk 2 from B to C Move disk 1 from A to C Total moves: 7
Binary Search (Recursive)
Found 23 at index 5 50 not found
Recursion vs Iteration
| Aspect | Recursion | Iteration |
|---|---|---|
| Memory | Uses stack frames (O(n) space) | Constant space (O(1)) |
| Speed | Slower (function call overhead) | Faster |
| Readability | Often cleaner for recursive problems | Better for simple loops |
| Risk | Stack overflow for deep recursion | No stack risk |
| Best for | Trees, divide-and-conquer, self-similar | Linear sequences, simple counts |
Same Problem: Iterative vs Recursive
Iterative: 10! = 3628800 Recursive: 10! = 3628800
Tail Recursion
A recursive call is "tail recursive" when it's the very last operation in the function. Some compilers can optimize tail recursion into iteration (eliminating stack growth):
#include <stdio.h>
// Not tail recursive (multiplication AFTER the recursive call)
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // Must multiply after call returns
}
// Tail recursive (result accumulates in parameter)
int factorialTail(int n, int accumulator) {
if (n <= 1) return accumulator;
return factorialTail(n - 1, n * accumulator); // Nothing after this call
}
int main() {
printf("Regular: 10! = %d\n", factorial(10));
printf("Tail: 10! = %d\n", factorialTail(10, 1));
return 0;
}Regular: 10! = 3628800 Tail: 10! = 3628800
Common Pitfalls
Missing Base Case (Infinite Recursion)
// WRONG: No base case — infinite recursion!
void infinite(int n) {
printf("%d ", n);
infinite(n + 1); // Never stops! → Stack Overflow
}
// CORRECT: Has a stopping condition
void finite(int n) {
if (n > 5) return; // Base case
printf("%d ", n);
finite(n + 1);
}Stack Overflow
Each recursive call adds a frame to the call stack. Too many calls exhaust stack memory:
#include <stdio.h>
// This will crash for large n (stack overflow)
int badRecursion(int n) {
if (n == 0) return 0;
return n + badRecursion(n - 1);
}
int main() {
// printf("%d\n", badRecursion(1000000)); // Stack overflow!
printf("%d\n", badRecursion(1000)); // OK for small n
return 0;
}Interview Questions
Q1: What are the two essential components of a recursive function?
Every recursive function needs: (1) A base case — a condition that stops the recursion and returns a value directly without further recursive calls; (2) A recursive case — the function calls itself with a modified (usually smaller) problem that progresses toward the base case.
Q2: What is stack overflow and how does recursion cause it?
Each recursive call creates a new stack frame (storing parameters, local variables, and return address). If recursion goes too deep (no base case reached, or problem is too large), the call stack exceeds its memory limit and the program crashes with a stack overflow. Default stack size is typically 1-8 MB.
Q3: What is the time complexity of naive recursive Fibonacci?
O(2^n) — exponential. Each call branches into two more calls, creating a binary tree of calls. fib(30) makes over a million calls. The fix is memoization (storing previously computed results) or converting to iteration, both achieving O(n).
Q4: What is tail recursion and why is it important?
Tail recursion is when the recursive call is the last operation in the function — nothing happens after it returns. Compilers can optimize tail recursion into a loop (tail call optimization), eliminating stack growth and making it as efficient as iteration. Not all compilers do this in C.
Q5: Can every recursive function be converted to iterative?
Yes, theoretically every recursive algorithm can be implemented iteratively, sometimes using an explicit stack data structure. However, some problems (like tree traversals, Tower of Hanoi, quicksort) are much more naturally and cleanly expressed with recursion.
Summary
- Recursion is a function calling itself with a smaller sub-problem
- Every recursive function needs a base case (stopping condition) and a recursive case
- Each call creates a stack frame — deep recursion can cause stack overflow
- Classic examples: factorial, Fibonacci, Tower of Hanoi, binary search
- Recursion is ideal for tree structures, divide-and-conquer, and self-similar problems
- Tail recursion can be optimized by compilers to eliminate stack growth
- For simple linear problems, iteration is usually more efficient
- Always ensure the recursive case makes progress toward the base case
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Recursion in C Programming.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this C Programming topic.
Search Terms
c-programming, c programming, programming, functions, recursion, recursion in c programming
Related C Programming Topics