Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT
Intermediate+150 XP

Unit 08 — Recursion

A function that calls itself. The concept that trips up most beginners — explained step by step, traced visually, and built up from the simplest example to the legendary Tower of Hanoi.

90 min March 2026
UNIT 08Prerequisite: Unit 06 — Stacks90 min read

Recursion is one of those ideas that sounds impossible the first time you hear it. A function that calls itself? Does it not just loop forever? How does it ever stop? How does it know what to return?

These are exactly the right questions. By the end of this unit every one of them will have a clear, satisfying answer. We will build the idea from a simple analogy, trace every call on the stack by hand, solve five classic problems, and understand why recursion is one of the most powerful tools in all of programming.

// Section 1

What is Recursion?

Recursion is when a function solves a problem by breaking it into a smaller version of the same problem and calling itself to solve that smaller version. It keeps breaking the problem down until it reaches a version so small it can be answered directly — without any further calls. That stopping point is called the base case.

🪞 The mirror analogy

Stand between two mirrors facing each other. You see yourself reflected, and inside that reflection you see another reflection, and inside that another — going smaller and smaller until they become too small to see.

Recursion works the same way. Each call creates a smaller version of itself, which creates an even smaller one, going deeper and deeper — until we hit the base case (the point where the mirrors end) and start coming back.

🛑
Law 1 — The Base Case
Every recursive function MUST have a condition where it stops calling itself and returns a direct answer. Without this, it runs forever and crashes with a stack overflow.
🔁
Law 2 — The Recursive Case
Every recursive call must move closer to the base case. Each call should work on a smaller version of the problem — never the same or larger. Otherwise it never terminates.
⚠️ Important
Missing the base case is the #1 recursion mistake. If you forget it or write it incorrectly, the function calls itself infinitely, fills up the call stack, and your program crashes with "Segmentation fault" or "Stack overflow." Always write the base case first — before writing the recursive case.
// Section 2

How Recursion Works Inside — The Call Stack

Remember the call stack from Unit 06? When a function calls another function, the current function is paused and pushed onto the call stack. When the called function returns, the paused function resumes from where it stopped. Recursion uses this exact mechanism — but the function calls itself.

Recursion has two clear phases. Going down — each call pushes a new frame and makes the problem smaller. Coming back up — each call finishes and returns its answer to the call that made it. The final answer bubbles all the way back to the very first call.

Tracing factorial(3) — call stack at each stage
📉 Going down (building up)
factorial(3)
n = 3
WAITING
factorial(2)
n = 2
WAITING
factorial(1)
n = 1
WAITING
factorial(0)
n = 0
ACTIVE
returns 1
base case hit → start returning
📈 Coming back up (answers bubble)
factorial(3)
n = 3
ACTIVE
returns 6
factorial(2)
n = 2
DONE
returns 2
factorial(1)
n = 1
DONE
returns 1
factorial(0)
n = 0
DONE
returns 1
final answer = 6 returned to caller
🎯 Pro Tip
The key insight: Each recursive call is completely independent. It has its own copy of local variables and its own position in the code. When factorial(2) calls factorial(1), factorial(2) is frozen mid-execution, waiting. Only when factorial(1) returns does factorial(2) resume and finish. This is what the call stack manages for you automatically.
// Section 3

Problem 1 — Factorial

Factorial of n (written as n!) means multiply all integers from 1 to n together. 5! = 5 × 4 × 3 × 2 × 1 = 120. And 0! = 1 by definition. This is the perfect first recursion problem because the pattern is obvious: n! = n × (n-1)!. The problem contains a smaller version of itself.

Breaking down factorial(4):
factorial(4)=4 × factorial(3)
factorial(3)=3 × factorial(2)
factorial(2)=2 × factorial(1)
factorial(1)=1 × factorial(0)
factorial(0)=1 ← BASE CASE
Unwinding: 1 → 1×1=1 → 2×1=2 → 3×2=6 → 4×6=24 ✓
c
#include <stdio.h>
int factorial(int n) {
/* base case: factorial of 0 is 1 — stop here */
if (n == 0) {
return 1;
}
/* recursive case: n! = n × (n-1)! */
return n * factorial(n - 1);
}
int main() {
printf("factorial(0) = %d\n", factorial(0)); /* 1 */
printf("factorial(1) = %d\n", factorial(1)); /* 1 */
printf("factorial(5) = %d\n", factorial(5)); /* 120 */
printf("factorial(7) = %d\n", factorial(7)); /* 5040 */
return 0;
}
Time:O(n)
Space:O(n)(n frames on call stack)
// Section 4

Problem 2 — Fibonacci

The Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21 ... Each number is the sum of the two before it. fib(n) = fib(n-1) + fib(n-2). Two base cases: fib(0) = 0 and fib(1) = 1.

c
#include <stdio.h>
int fibonacci(int n) {
/* two base cases */
if (n == 0) return 0;
if (n == 1) return 1;
/* recursive case: sum of the two before it */
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int i;
printf("Fibonacci sequence: ");
for (i = 0; i < 10; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");
/* Output: 0 1 1 2 3 5 8 13 21 34 */
return 0;
}

⚠ Why this is dangerously slow

Computing fib(5) looks harmless — but look at how many times the same values get computed repeatedly:

fib(5)
├── fib(4)
│ ├── fib(3) ← computed again below
│ │ ├── fib(2)
│ │ └── fib(1)
│ └── fib(2) ← computed again below
└── fib(3) ← same as above, recomputed
├── fib(2) ← same again
└── fib(1)
Time:O(2ⁿ)
fib(50) requires over 1 trillion calls!
💡 Note
The fix is Dynamic Programming — store answers you have already computed so you never recompute them. We cover this completely in Unit 16. For now, understand the pattern and the problem. Naive recursive Fibonacci is a perfect example of why O(2ⁿ) is catastrophic at scale.
// Section 5

Problem 3 — Sum of Digits

Given a number like 1234, find the sum of its digits: 1+2+3+4 = 10. The recursive insight: the sum of digits of 1234 = last digit (4) + sum of digits of 123. Keep peeling off the last digit until the number is 0.

c
#include <stdio.h>
int sumOfDigits(int n) {
/* base case: no digits left */
if (n == 0) return 0;
/* recursive case: last digit + sum of remaining digits */
return (n % 10) + sumOfDigits(n / 10);
/* n % 10 gives the last digit: 1234 % 10 = 4 */
/* n / 10 removes the last digit: 1234 / 10 = 123 */
}
int main() {
printf("sumOfDigits(1234) = %d\n", sumOfDigits(1234)); /* 10 */
printf("sumOfDigits(9999) = %d\n", sumOfDigits(9999)); /* 36 */
printf("sumOfDigits(100) = %d\n", sumOfDigits(100)); /* 1 */
return 0;
}
sumOfDigits(1234)
= 4 + sumOfDigits(123)
= 4 + 3 + sumOfDigits(12)
= 4 + 3 + 2 + sumOfDigits(1)
= 4 + 3 + 2 + 1 + sumOfDigits(0)
= 4 + 3 + 2 + 1 + 0 ← base case
= 10 ✓
Time:O(d)where d = number of digits
// Section 6

Problem 4 — Power (x to the n)

Computing x^n (x raised to the power n). The naive way: multiply x by itself n times — O(n). The recursive insight: x^n = x × x^(n-1). Base case: x^0 = 1. And with one small improvement we can make it O(log n) — the fast power trick.

c
#include <stdio.h>
/* Simple version — O(n) */
int power(int base, int exp) {
if (exp == 0) return 1; /* x^0 = 1 — base case */
return base * power(base, exp - 1); /* x^n = x × x^(n-1) */
}
/* Fast version — O(log n) — splits the problem in half each time */
int fastPower(int base, int exp) {
if (exp == 0) return 1;
if (exp % 2 == 0) {
/* even exponent: x^n = (x^(n/2))^2 */
int half = fastPower(base, exp / 2);
return half * half; /* compute half once, use twice */
} else {
/* odd exponent: x^n = x × x^(n-1) */
return base * fastPower(base, exp - 1);
}
}
int main() {
printf("2^10 = %d\n", power(2, 10)); /* 1024 */
printf("3^4 = %d\n", power(3, 4)); /* 81 */
printf("2^10 = %d (fast)\n", fastPower(2, 10)); /* 1024 */
printf("2^20 = %d (fast)\n", fastPower(2, 20)); /* 1048576 */
return 0;
}
🎯 Pro Tip
Fast power is O(log n) because each call divides the exponent by 2. To compute 2^20, the simple version makes 20 calls. The fast version makes only 5 — because 20 → 10 → 5 → 4 → 2 → 1. This same divide-in-half pattern appears in binary search, merge sort, and many other O(log n) algorithms.
// Section 7

Problem 5 — Greatest Common Divisor (GCD)

GCD of two numbers is the largest number that divides both of them exactly. GCD(12, 8) = 4. The Euclidean algorithm is one of the oldest algorithms in history — and it is naturally recursive. The insight: GCD(a, b) = GCD(b, a % b). Keep taking the remainder until b becomes 0. At that point, a is the GCD.

c
#include <stdio.h>
int gcd(int a, int b) {
/* base case: when b is 0, GCD is a */
if (b == 0) return a;
/* recursive case: GCD(a, b) = GCD(b, a % b) */
return gcd(b, a % b);
}
int main() {
printf("GCD(12, 8) = %d\n", gcd(12, 8)); /* 4 */
printf("GCD(48, 18) = %d\n", gcd(48, 18)); /* 6 */
printf("GCD(100, 75)= %d\n", gcd(100, 75)); /* 25 */
return 0;
}
Tracing gcd(48, 18):
gcd(48, 18) → gcd(18, 48%18) = gcd(18, 12)
gcd(18, 12) → gcd(12, 18%12) = gcd(12, 6)
gcd(12, 6) → gcd(6, 12%6) = gcd(6, 0)
gcd(6, 0) → return 6 ← base case ✓
// Section 8

Recursion vs Iteration — When to Use Which

Every recursive solution can be rewritten as an iterative one (with loops), and vice versa. They are equally powerful. The choice depends on which makes the code clearer and more maintainable for a given problem.

Use Recursion when:
The problem is naturally self-similar (trees, graphs, divide & conquer)
Code clarity matters more than performance
The depth of recursion is small and bounded
Working with tree traversal, backtracking, or DFS
Use Iteration when:
Performance is critical and stack depth could be large
The problem is a simple loop — no natural self-similarity
You want to avoid stack overflow on large inputs
Simple traversals: arrays, linked lists, strings

Same problem — two solutions side by side

c
/* Recursive factorial — elegant, uses call stack */
int factorialRecursive(int n) {
if (n == 0) return 1;
return n * factorialRecursive(n - 1);
}
/* Iterative factorial — uses a loop, no extra stack space */
int factorialIterative(int n) {
int result = 1;
int i;
for (i = 1; i <= n; i++) {
result = result * i;
}
return result;
}
/* Both give identical answers — different tradeoffs:
Recursive: cleaner code, O(n) space (call stack)
Iterative: slightly more code, O(1) space (just one variable)
*/
// Section 9

Tower of Hanoi — The Legendary Recursion Puzzle

Tower of Hanoi is the most famous recursion problem in all of computer science. It has three rods and n disks of different sizes stacked on the first rod — largest at the bottom, smallest at the top. The goal: move all disks to the third rod.

Three strict rules:
1
Only one disk can be moved at a time
2
Only the top disk on any rod can be moved
3
A larger disk can never be placed on top of a smaller disk
// n=3 disks — move all from Rod A to Rod C, using Rod B as helper
A (Source)
disk 3 (large)
disk 2
disk 1 (small)
B (Helper)
C (Target)
The recursive insight — think in 3 steps:
1
Move the top n-1 disks from source to helper (using target as intermediate)
2
Move the largest disk (disk n) directly from source to target
3
Move the n-1 disks from helper to target (using source as intermediate)

Step 1 and Step 3 are the recursive calls — the same problem on n-1 disks. Step 2 is the base work. When n = 1 (just one disk), move it directly — that is the base case. The magic: you never need to think about which specific disk goes where. Just trust the three steps — recursion handles all the details.

c
#include <stdio.h>
/*
* hanoi(n, source, target, helper)
* Move n disks from source rod to target rod, using helper rod
*/
void hanoi(int n, char source, char target, char helper) {
/* base case: only one disk — move it directly */
if (n == 1) {
printf("Move disk 1 from rod %c to rod %c\n", source, target);
return;
}
/* step 1: move top n-1 disks from source to helper */
hanoi(n - 1, source, helper, target);
/* step 2: move the largest disk from source to target */
printf("Move disk %d from rod %c to rod %c\n", n, source, target);
/* step 3: move n-1 disks from helper to target */
hanoi(n - 1, helper, target, source);
}
int main() {
printf("Tower of Hanoi with 3 disks:\n");
hanoi(3, 'A', 'C', 'B');
return 0;
}
output
/* Output for n=3 (7 moves — minimum possible):
Move disk 1 from rod A to rod C
Move disk 2 from rod A to rod B
Move disk 1 from rod C to rod B
Move disk 3 from rod A to rod C
Move disk 1 from rod B to rod A
Move disk 2 from rod B to rod C
Move disk 1 from rod A to rod C
*/
Minimum moves needed:
n=1
1
n=2
3
n=3
7
n=4
15
n=10
1,023
n=64
1.8 × 10¹⁹
Formula: 2ⁿ - 1 moves. Time complexity: O(2ⁿ) — exponential and unavoidable for this problem.
📌 Real World Example
Ancient legend: There is a temple in Hanoi with 64 golden disks. Monks move one disk per second following the rules. When they finish — the world ends. At 2⁶⁴ - 1 moves and one per second, that is about 585 billion years. The universe is only 14 billion years old. This is what exponential complexity looks like in real life.
// Section 10

Errors You Will Hit

Missing base case — infinite recursion
Symptom: Program crashes with "Segmentation fault" — stack overflow from infinite calls
Fix: Always write the base case first. Ask: what is the simplest possible input that can be answered directly?
❌ Wrong
int factorial(int n) { return n * factorial(n - 1); /* no base case — infinite! */ }
✅ Correct
int factorial(int n) { if (n == 0) return 1; /* base case first */ return n * factorial(n - 1); }
Recursive case does not move toward base case
Symptom: Same infinite crash — each call is no closer to stopping
Fix: The input to the recursive call must be smaller/simpler than the current input. If n goes in, n-1 or n/2 must go into the recursive call.
❌ Wrong
int badFunc(int n) { if (n == 0) return 0; return n + badFunc(n); /* same n — never reaches 0 */ }
✅ Correct
int goodFunc(int n) { if (n == 0) return 0; return n + goodFunc(n - 1); /* n-1 — moves toward 0 */ }
Not returning the recursive call result
Symptom: Function returns garbage or 0 even though logic looks correct
Fix: Always return the result of the recursive call. Forgetting return means the answer gets computed but thrown away.
❌ Wrong
int factorial(int n) { if (n == 0) return 1; n * factorial(n - 1); /* computed but not returned! */ }
✅ Correct
int factorial(int n) { if (n == 0) return 1; return n * factorial(n - 1); /* return is essential */ }
// What's Next

You Are Ready for Unit 09

You now understand recursion completely — the base case, recursive case, how the call stack manages each frame, and why some recursive solutions are dangerously slow. You traced factorial, Fibonacci, sum of digits, power, GCD, and the legendary Tower of Hanoi.

In Unit 09 we cover Sorting Algorithms — six different ways to arrange data in order, from the simplest to the fastest. Bubble sort, selection sort, insertion sort, merge sort, quick sort, counting sort. Each explained with step-by-step visuals and full C code.

UP NEXT → UNIT 09
Sorting Algorithms — Six Ways to Sort
Bubble, selection, insertion, merge, quick, counting — with Big O comparison.
Coming Soon →

🎯 Key Takeaways

  • Recursion = a function that calls itself on a smaller version of the problem until it hits a base case
  • Two laws: base case (where it stops) and recursive case (must move closer to base case each call)
  • The call stack manages recursion automatically — each call gets its own frame, frozen until it returns
  • Recursion has two phases: going down (building up calls) and coming back up (returning answers)
  • Factorial: n! = n × (n-1)! — base case n=0 returns 1
  • Fibonacci naive is O(2ⁿ) because it recomputes the same values — fixed by Dynamic Programming in Unit 16
  • Fast power uses divide-and-conquer to compute x^n in O(log n) instead of O(n)
  • GCD(a, b) = GCD(b, a%b) — keep taking remainder until b=0, then a is the answer
  • Tower of Hanoi: move n-1 to helper, move largest to target, move n-1 to target — O(2ⁿ) moves
  • Always write base case first. Always return the recursive call result. Always move toward the base case
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...