Functions
Recursion
Complete lesson for Recursion using LEARNCODEGURU’s 6-level learning model — from basics to advanced practice, with C++ OOP and STL where relevant.
Basics — Recursion
Section: Functions. Recursion in C++ — base case, recursive case, call stack, applications, and classic examples.
What is recursion?
- **Recursion** is when a function **calls itself** (directly or indirectly) to solve a problem by breaking it into smaller similar subproblems.
- Every useful recursive function needs two parts: a **base case** (stop) and a **recursive case** (call with a smaller input).
- Each call waits for the smaller call to finish — results combine as the call stack unwinds.
- **Real-life analogy:** opening nested gift boxes — open the outer box, then the next, until the last empty/base box, then close them in reverse.
- **Real-life analogy:** a manager delegates a task to a junior, who delegates further, until someone does the tiny base job and reports back up.
| Part | Role | Example (factorial) |
|---|---|---|
| Base case | Stops further calls | if (n <= 1) return 1; |
| Recursive case | Calls itself with smaller input | return n * fact(n - 1); |
| Progress | Must move toward the base | n decreases each call |
| Unwinding | Combine results returning up | 5*4*3*2*1 |
Recursion — flow, examples, and use cases
Recursion solves a problem by calling the same function on a smaller input until a base case stops. The diagram covers the call flow, factorial / Fibonacci / sum / Tower of Hanoi, recursion vs iteration, and common applications.
- Base case: stops further calls (e.g.
n <= 1). - Recursive case: call with a smaller input and combine results.
- Stack: each call uses a frame — too deep causes stack overflow.
- Use when: trees, graphs, backtracking, divide & conquer, puzzles.
How recursion works (call stack)
- Each recursive call pushes a new **stack frame** (parameters, locals, return address).
- When the base case returns, frames pop and values flow back to the callers.
- Too many frames → **stack overflow** (missing/wrong base case, or huge depth).
- Trace with a small
non paper before coding larger cases.
// Trace factorial(4):
// fact(4) -> 4 * fact(3)
// fact(3) -> 3 * fact(2)
// fact(2) -> 2 * fact(1)
// fact(1) -> 1 // base
// then 2*1, 3*2, 4*6 => 24 Factorial (classic example)
n! = n × (n−1) × … × 1, with0! = 1and1! = 1.- **Real-life:** arranging
ndistinct books on a shelf (permutations of n items ≈ related counting ideas).
#include <iostream>
using namespace std;
long long factorial(int n) {
if (n == 0 || n == 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
int main() {
cout << factorial(5) << "\n"; // 120
return 0;
} Fibonacci series
fib(0)=0,fib(1)=1,fib(n)=fib(n-1)+fib(n-2).- Simple recursion is clear but **slow** for large
n(many repeated calls) — later use memoization/DP or iteration. - **Real-life:** idealized rabbit-pair growth model; also appears in nature-ish patterns and algorithms.
#include <iostream>
using namespace std;
int fib(int n) {
if (n <= 1) return n; // base
return fib(n - 1) + fib(n - 2);
}
int main() {
for (int i = 0; i <= 7; i++) cout << fib(i) << " ";
// 0 1 1 2 3 5 8
return 0;
} Sum of first N naturals
sum(n) = n + sum(n-1), basesum(0)=0.- **Real-life:** total score after n rounds if you add the latest round to the sum of previous rounds.
int sum(int n) {
if (n == 0) return 0;
return n + sum(n - 1);
}
// sum(5) = 15 Tower of Hanoi
- Move
ndisks from peg A to C using helper B — never put a larger disk on a smaller one. - Recursive idea: move
n-1to helper, move largest to target, moven-1onto target. - **Real-life / puzzle:** classic temple puzzle; teaches divide-and-conquer thinking.
- Number of moves =
2^n - 1(for n=3 → 7 moves).
#include <iostream>
using namespace std;
void tower(int n, char from, char to, char aux) {
if (n == 0) return; // base
tower(n - 1, from, aux, to);
cout << "Move disk " << n << " from " << from << " to " << to << "\n";
tower(n - 1, aux, to, from);
}
int main() {
tower(3, 'A', 'C', 'B');
return 0;
} Applications of recursion (with examples)
- Use recursion when the problem **naturally splits** into smaller copies of itself.
- Especially strong for trees, graphs, backtracking, and divide-and-conquer algorithms.
| Application area | Why recursion fits | Concrete example |
|---|---|---|
| Mathematics | Definition refers to smaller n | Factorial, Fibonacci, GCD (Euclid) |
| Divide & conquer | Split, solve, combine | Merge sort, quick sort, binary search |
| Trees | Child nodes are smaller trees | Inorder / preorder / postorder traversal |
| Graphs | Explore neighbors recursively | DFS path finding |
| Backtracking | Try → recurse → undo | N-Queens, maze path, sudoku |
| Parsing / nested data | Nested structure | Nested folders, nested lists, JSON-like trees |
| Puzzles | State reduces step by step | Tower of Hanoi |
| Dynamic programming | Recursion + memoization | Fib with cache, grid paths |
| String problems | Shrink string each call | Palindrome check, reverse string |
| Combinatorics | Choose / skip decisions | Generate subsets / permutations |
Application examples in code
- **GCD (Euclid):**
gcd(a,b)=gcd(b,a%b)untilb==0. - **Palindrome:** compare ends, recurse on the inner substring/indices.
- **Binary search:** mid compare, recurse left or right half.
- **Folder size (idea):** size(folder) = sum of size(child) — tree recursion.
#include <iostream>
#include <string>
using namespace std;
int gcd(int a, int b) {
if (b == 0) return a; // base
return gcd(b, a % b);
}
bool isPalindrome(const string& s, int l, int r) {
if (l >= r) return true; // base
if (s[l] != s[r]) return false;
return isPalindrome(s, l + 1, r - 1);
}
int binarySearch(int a[], int lo, int hi, int key) {
if (lo > hi) return -1; // base: not found
int mid = lo + (hi - lo) / 2;
if (a[mid] == key) return mid;
if (key < a[mid]) return binarySearch(a, lo, mid - 1, key);
return binarySearch(a, mid + 1, hi, key);
}
int main() {
cout << gcd(48, 18) << "\n"; // 6
string w = "level";
cout << (isPalindrome(w, 0, (int)w.size() - 1) ? "Yes\n" : "No\n");
int a[] = {1, 3, 5, 7, 9};
cout << binarySearch(a, 0, 4, 7) << "\n"; // 3
return 0;
} Differences between recursion and iteration
- Both can repeat work; choose based on clarity, depth, and performance.
| Point | Recursion | Iteration (loops) |
|---|---|---|
| Approach | Function calls itself | Uses for / while |
| Code size | Often shorter for trees/Hanoi | Often longer for nested structures |
| Memory | Extra stack frames | Usually constant extra memory |
| Risk | Stack overflow if too deep | No call-stack growth from calls |
| Speed | Can be slower (call overhead / repeats) | Often faster for simple counts |
| Best for | Trees, graphs, backtracking, D&C | Simple counting / linear scans |
When to use recursion vs a loop
- Prefer recursion when the data/problem is hierarchical or self-similar.
- Prefer loops for simple 1..n accumulation, scanning arrays, and when depth may be huge.
- You can often rewrite recursion as iteration with an explicit stack — advanced but useful.
Advanced — Recursion
Direct vs indirect recursion
- **Direct:**
fcallsf. - **Indirect:**
fcallsg, andgcallsf(or a longer cycle). - Same rules: clear base cases and progress toward termination.
bool isEven(int n);
bool isOdd(int n);
bool isEven(int n) {
if (n == 0) return true;
return isOdd(n - 1);
}
bool isOdd(int n) {
if (n == 0) return false;
return isEven(n - 1);
} Tail recursion and stack pressure
- A **tail-recursive** call is the last action in the function — some languages optimize it; C++ does **not** guarantee TCO.
- Deep recursion on large
ncan still overflow — use iteration or increase design limits carefully. - Naive Fibonacci is exponential time — add **memoization** (cache results) or use a loop.
#include <vector>
using namespace std;
long long fibMemo(int n, vector<long long>& memo) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
return memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
} Backtracking pattern (application)
- Choose an option → recurse → undo the choice (backtrack) → try next option.
- **Real-life:** maze — walk a path, hit a dead end, step back, try another corridor.
- Used in N-Queens, permutations, subset sum, crossword-like fills.
#include <iostream>
#include <vector>
using namespace std;
void subsets(vector<int>& nums, int i, vector<int>& cur) {
if (i == (int)nums.size()) {
cout << "{ ";
for (int x : cur) cout << x << " ";
cout << "}\n";
return;
}
// exclude nums[i]
subsets(nums, i + 1, cur);
// include nums[i]
cur.push_back(nums[i]);
subsets(nums, i + 1, cur);
cur.pop_back(); // backtrack
} Tree recursion (application idea)
- A tree node’s work often = process node + recurse(left) + recurse(right).
- **Real-life:** company org chart — total headcount(manager) = 1 + sum(headcount(report)).
- **Real-life:** disk usage — folder size = file sizes + size of subfolders.
Safety checklist
- Always write the base case first.
- Ensure every recursive path moves closer to the base (smaller n, narrower range, shorter string).
- Test
n=0,n=1, and one medium value with a paper trace. - Watch integer overflow for factorial / fib of larger inputs (
long long). - If depth may be thousands+, prefer iteration or an explicit stack.
Tricky Interview Points
Q1) What is recursion?
Answer A technique where a function solves a problem by calling itself on a smaller subproblem until a base case stops the calls.
Q2) What are base case and recursive case?
Answer Base case returns a result without further calls. Recursive case calls the function again with a smaller/simpler input and combines the result.
Q3) What happens if there is no base case?
Answer Recursion never stops; the call stack grows until stack overflow (or the program is killed).
Q4) Recursion vs iteration?
Answer Recursion uses function calls and stack memory; iteration uses loops and usually constant extra memory. Recursion is often clearer for trees/backtracking; loops are often better for simple linear tasks.
Q5) What is a call stack in recursion?
Answer The runtime stack of frames for each active call. Frames push on call and pop on return, which is how values unwind.
Q6) Give applications of recursion.
Answer Factorial/Fibonacci/GCD, Tower of Hanoi, binary search, tree/graph DFS, merge/quick sort, backtracking (N-Queens, maze), generating subsets/permutations, nested folder processing.
Q7) Why is naive recursive Fibonacci slow?
Answer It recomputes the same subproblems many times (exponential calls). Fix with memoization/DP or an iterative loop.
Q8) What is Tail recursion?
Answer When the recursive call is the last operation in the function. Some languages optimize it; C++ does not guarantee tail-call optimization.
Q9) How many moves does Tower of Hanoi need for n disks?
Answer 2^n − 1 moves (n=3 → 7).
Q10) Direct vs indirect recursion?
Answer Direct: a function calls itself. Indirect: functions call each other in a cycle (f→g→f).
Q11) When should you avoid recursion?
Answer When depth can be very large, when a simple loop is clearer/faster, or when stack space is limited — unless you control depth or convert to iteration.
Q12) How do you reverse a string using recursion?
Answer Swap/print ends and recurse on the inner range, or recurse on the suffix and append the first character on the way back.
Simple Programs
Factorial
#include <iostream>
using namespace std;
long long fact(int n) {
if (n <= 1) return 1;
return n * fact(n - 1);
}
int main() {
cout << fact(6) << "\n"; // 720
return 0;
} Fibonacci print first N terms
#include <iostream>
using namespace std;
int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
int main() {
int n = 8;
for (int i = 0; i < n; i++) cout << fib(i) << " ";
return 0;
} Sum of digits (recursive)
#include <iostream>
using namespace std;
int sumDigits(int n) {
if (n == 0) return 0;
return (n % 10) + sumDigits(n / 10);
}
int main() {
cout << sumDigits(1234) << "\n"; // 10
return 0;
} Power x^n (recursive)
#include <iostream>
using namespace std;
long long power(long long x, int n) {
if (n == 0) return 1;
return x * power(x, n - 1);
}
int main() {
cout << power(2, 10) << "\n"; // 1024
return 0;
} Tower of Hanoi (n = 3)
#include <iostream>
using namespace std;
void hanoi(int n, char a, char c, char b) {
if (n == 0) return;
hanoi(n - 1, a, b, c);
cout << "Move " << n << ": " << a << " -> " << c << "\n";
hanoi(n - 1, b, c, a);
}
int main() {
hanoi(3, 'A', 'C', 'B');
return 0;
} GCD — Euclid recursion
#include <iostream>
using namespace std;
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int main() {
cout << gcd(84, 30) << "\n"; // 6
return 0;
} Recursive binary search
#include <iostream>
using namespace std;
int bsearch(int a[], int lo, int hi, int key) {
if (lo > hi) return -1;
int mid = lo + (hi - lo) / 2;
if (a[mid] == key) return mid;
if (key < a[mid]) return bsearch(a, lo, mid - 1, key);
return bsearch(a, mid + 1, hi, key);
}
int main() {
int a[] = {2, 4, 6, 8, 10, 12};
cout << bsearch(a, 0, 5, 10) << "\n";
return 0;
} Problem Solving Tricks
- Write the base case first — then the recursive step.
- Every call must move closer to the base (smaller n / narrower range).
- Trace n=0,1,2,3 on paper before trusting larger inputs.
- Prefer long long for factorial/fib growth.
- Naive fib → memoize or iterate for speed.
- Recursion shines for trees, backtracking, Hanoi, D&C sorts.
- Use loops for simple linear scans to save stack and time.
- Indirect recursion still needs a clear stopping condition.
- Backtracking = choose → recurse → undo.
- Stack overflow usually means missing base case or huge depth.
- Name functions after the problem: factorial, gcd, hanoi — not rec1.
- Compare recursion vs loop mentally: which maps the problem structure better?
Practice Programs List
- Write recursive factorial and verify 0!, 1!, 5!, 10!.
- Print the first 10 Fibonacci numbers using recursion.
- Recursive sum of first n naturals; compare with formula n(n+1)/2.
- Recursive sum of digits of an integer.
- Recursive reverse of a positive integer’s digits (print or build a number).
- Implement Euclid GCD recursively; test several pairs.
- Tower of Hanoi for n=1,2,3 and count the printed moves.
- Recursive palindrome check for a string using indices l,r.
- Recursive binary search on a sorted array.
- Recursive power x^n; then try a faster version using x^(n/2).
- Generate all subsets of {1,2,3} with backtracking recursion.
- Rewrite factorial with a loop and compare clarity/performance.
- Explain in comments a full call-stack trace of factorial(4).
- List five real applications of recursion and one example each.