Competitive Coding

Unit 3: Recursion & Advanced Techniques

From base cases to backtracking โ€” master recursive thinking, optimise with memoization, and solve competition-level problems that unlock high-paying tech careers.

โฑ๏ธ Time: 7 hrs theory + 5 hrs practice  |  ๐Ÿ’ฐ Earning: โ‚น8Kโ€“โ‚น30K/month  |  ๐Ÿ“ 30 MCQs (Bloom's Mapped)

๐Ÿ’ผ Jobs this unlocks: SDE-1 (โ‚น8โ€“15 LPA)  |  Competitive Programmer (โ‚น10โ€“25 LPA)  |  Algorithm Engineer (โ‚น12โ€“30 LPA)

Section A

Opening Hook โ€” The Invisible Power Behind Every Search

๐Ÿ—บ๏ธ How Google Maps Finds the Shortest Path in Milliseconds

Every time you open Google Maps and ask for directions, a recursive algorithm called Depth-First Search (DFS) explores a graph of 1 billion+ nodes โ€” intersections, roads, and landmarks across the planet. The algorithm recursively explores each path, backtracks when it hits a dead end, and finds the optimal route in under 200 milliseconds. That's recursion at planetary scale.

But recursion isn't just for tech giants. Every file explorer on your laptop โ€” Windows Explorer, macOS Finder, or Linux's ls -R โ€” uses recursive directory traversal. When you click "Search this PC," the OS recursively enters each folder, checks its contents, enters sub-folders, checks again... until every nested file is found. That's a recursive function calling itself on every subdirectory.

What if YOU could think recursively? What if you could take a massive problem, break it into identical smaller pieces, and let the computer solve billions of sub-problems automatically? That's exactly what this chapter teaches you โ€” the most elegant and powerful problem-solving technique in all of computer science.

๐ŸŒ Google๐Ÿ›’ Amazon๐Ÿ’ป Microsoft๐Ÿ† Codeforces๐Ÿ’ก LeetCode๐Ÿงฉ HackerRank
Recursion exists in nature. Romanesco broccoli grows in a fractal spiral โ€” each small cone is a miniature replica of the whole. Tree branches split recursively: a trunk splits into branches, each branch splits into smaller branches, each twig splits into leaves. Even your lungs use recursive branching โ€” 23 levels of branching airways to maximize surface area in a compact space. Nature solved recursion billions of years before programmers did.
Section B

Learning Outcomes โ€” Bloom's Taxonomy Mapped (12 Outcomes)

Bloom's LevelLearning Outcome
๐Ÿ”ต RememberDefine recursion and identify the two essential components: base case and recursive case
๐Ÿ”ต RememberList and distinguish the types of recursion: direct, indirect, tail, and non-tail
๐Ÿ”ต UnderstandExplain how the call stack works during recursive function execution, including push/pop of stack frames
๐Ÿ”ต UnderstandDescribe how memoization eliminates overlapping subproblems and reduces exponential time to linear
๐ŸŸข ApplyWrite recursive solutions for factorial, Fibonacci, power(x,n), and sum-of-digits problems in C++ and Python
๐ŸŸข ApplyImplement a backtracking solution for the N-Queens problem with constraint checking
๐ŸŸข AnalyzeCompare tail recursion vs non-tail recursion in terms of stack usage, performance, and compiler optimisation
๐ŸŸข AnalyzeAnalyze the time complexity difference between naive recursive Fibonacci O(2โฟ) and memoized Fibonacci O(n)
๐ŸŸ  EvaluateJudge when recursion is a better choice than iteration, considering readability, performance, and stack limits
๐ŸŸ  EvaluateEvaluate the trade-offs between top-down memoization and bottom-up tabulation for dynamic programming
๐ŸŸ  CreateDesign a recursive Sudoku solver that uses backtracking with constraint propagation
๐ŸŸ  CreateBuild a complete backtracking solution for the subset-sum problem with pruning optimisation
Section C

Concept Explanation โ€” Recursion & Advanced Techniques from Scratch

1. Introduction to Recursion

Plain English: Recursion is when a function calls itself to solve a smaller version of the same problem. Imagine you're standing in a queue and want to know your position. You ask the person in front, "What's your position?" They ask the person in front of them, and so on โ€” until the first person says "I'm #1." Then the answers cascade back: #2, #3, #4... That's recursion.

Technical Definition: A recursive function is a function that calls itself with modified arguments, working toward a base case that terminates the recursion. Every recursive function has exactly two components:

๐Ÿ”„ The Two Essential Components of Recursion

1. Base Case (Termination Condition): The condition under which the function stops calling itself and returns a known value directly. Without this, the function recurses forever.

2. Recursive Case (Self-call): The function calls itself with a smaller or simpler input, moving closer to the base case with each call.

Factorial Example โ€” The "Hello World" of Recursion

The factorial of n (written n!) is: n! = n ร— (n-1) ร— (n-2) ร— ... ร— 1, with 0! = 1.

C++
// Recursive Factorial in C++
#include <iostream>
using namespace std;

int factorial(int n) {
    // Base case: 0! = 1
    if (n == 0) return 1;
    // Recursive case: n! = n * (n-1)!
    return n * factorial(n - 1);
}

int main() {
    cout << "5! = " << factorial(5) << endl;
    return 0;
}
Python
# Recursive Factorial in Python
def factorial(n):
    # Base case
    if n == 0:
        return 1
    # Recursive case
    return n * factorial(n - 1)

print(f"5! = {factorial(5)}")
5! = 120

How the Call Stack Works โ€” ASCII Stack Diagram

When factorial(4) is called, each recursive call is pushed onto the call stack. When the base case is reached, results pop back up:

CALL PHASE (pushing frames): RETURN PHASE (popping frames): factorial(4) factorial(4) = 4 ร— 6 = 24 โ† FINAL โ””โ”€ factorial(3) โ””โ”€ factorial(3) = 3 ร— 2 = 6 โ””โ”€ factorial(2) โ””โ”€ factorial(2) = 2 ร— 1 = 2 โ””โ”€ factorial(1) โ””โ”€ factorial(1) = 1 ร— 1 = 1 โ””โ”€ factorial(0) โ””โ”€ factorial(0) = 1 โ† BASE CASE return 1 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ CALL STACK โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ factorial(0) โ†’ returns 1 โ”‚ โ† TOP (most recent call) โ”‚ factorial(1) โ†’ waiting... โ”‚ โ”‚ factorial(2) โ†’ waiting... โ”‚ โ”‚ factorial(3) โ†’ waiting... โ”‚ โ”‚ factorial(4) โ†’ waiting... โ”‚ โ† BOTTOM (original call) โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Recursion = Russian nesting dolls (matryoshka) at a Delhi market โ€” you open one doll, find another inside, open that, find another... until you reach the smallest doll (base case). Then you close them all back up in reverse order. Each doll is a stack frame, and the smallest doll is your base case that stops the recursion.

2. Base Condition โ€” The Emergency Brake of Recursion

The base case is the most critical part of any recursive function. It's the condition that says "STOP โ€” don't call yourself again." Without it, the function calls itself infinitely until the program crashes.

What Happens Without a Base Case?

C++
// โŒ DANGEROUS: No base case!
void infinite(int n) {
    cout << n << " ";
    infinite(n - 1);  // Never stops!
}
// Output: 5 4 3 2 1 0 -1 -2 ... CRASH!
// Error: Stack Overflow (Segmentation Fault)

Stack Overflow Error: Each recursive call creates a new stack frame in memory. Without a base case, frames pile up endlessly until the stack memory is exhausted. On most systems, the default stack size is 1โ€“8 MB, which allows roughly 10,000โ€“100,000 recursive calls before crashing.

Forgetting the base case is the #1 recursion bug. Always write your base case FIRST before writing the recursive case. A good practice: before writing any recursive function, ask yourself: "What is the simplest input that I can answer directly without recursion?" That's your base case.

Rules for Writing Good Base Cases

RuleExample
Base case must be reachablefactorial(n): n decreases by 1 each call, eventually reaching 0
Base case must return a known valuefactorial(0) = 1 โ€” a known mathematical fact
Arguments must move toward the base caseIf base is n=0, each call must use n-1, not n+1
Sometimes you need multiple base casesFibonacci: fib(0)=0 AND fib(1)=1

3. Solving Problems Using Recursion

The Recursive Thinking Pattern: Every recursive solution follows this mental model:

  1. Assume the recursive call magically gives the correct answer for a smaller input
  2. Use that answer to build the solution for the current input
  3. Define the base case where you know the answer directly

Problem 1: Fibonacci Numbers

The Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21... Each number is the sum of the two preceding ones.

C++
// Recursive Fibonacci
int fib(int n) {
    if (n <= 0) return 0;       // Base case 1
    if (n == 1) return 1;       // Base case 2
    return fib(n-1) + fib(n-2); // Recursive case
}
Python
# Recursive Fibonacci
def fib(n):
    if n <= 0: return 0
    if n == 1: return 1
    return fib(n-1) + fib(n-2)

Recursion Tree for fib(5)

fib(5) / \ fib(4) fib(3) / \ / \ fib(3) fib(2) fib(2) fib(1) / \ / \ / \ | fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) 1 / \ | | | | fib(1) fib(0) 1 1 0 1 | | 1 0 Total calls: 15 | Unique subproblems: 6 | Time: O(2โฟ) Notice fib(3) is computed 2 times, fib(2) is computed 3 times โ€” WASTED WORK!

Problem 2: Power(x, n) โ€” Two Approaches

C++
// Naive: O(n) time
double power_naive(double x, int n) {
    if (n == 0) return 1;
    return x * power_naive(x, n - 1);
}

// Optimised: O(log n) time โ€” using divide and conquer
double power_fast(double x, int n) {
    if (n == 0) return 1;
    double half = power_fast(x, n / 2);
    if (n % 2 == 0)
        return half * half;
    else
        return half * half * x;
}

Problem 3: Sum of Digits

Python
# Sum of digits of a number
def sum_digits(n):
    if n == 0: return 0             # Base case
    return (n % 10) + sum_digits(n // 10)  # Last digit + sum of rest

print(sum_digits(9876))  # 9+8+7+6 = 30
30
Trace: sum_digits(9876) โ†’ 6 + sum_digits(987) โ†’ 7 + sum_digits(98) โ†’ 8 + sum_digits(9) โ†’ 9 + sum_digits(0) โ†’ 0 (base case) โ†’ 9 + 0 = 9 โ†’ 8 + 9 = 17 โ†’ 7 + 17 = 24 โ†’ 6 + 24 = 30 โœ“

4. Classic and Modern Approaches

Classic Recursion: A function calls itself directly to solve progressively smaller instances. Examples: factorial, fibonacci, sum-of-digits. The pattern is straightforward โ€” each call reduces the problem by a small amount (usually by 1).

Divide and Conquer: A more powerful pattern where the problem is split into independent subproblems of roughly equal size, solved recursively, and then combined. This often leads to O(n log n) algorithms instead of O(nยฒ).

AspectClassic RecursionDivide & Conquer
Problem splitReduce by 1 (or small constant)Split into 2+ equal halves
Subproblems1 subproblem2+ independent subproblems
Typical complexityO(n) or O(2โฟ)O(n log n)
ExamplesFactorial, Fibonacci, Sum of digitsMerge Sort, Quick Sort, Binary Search

Binary Search โ€” Recursive Version

C++
// Recursive Binary Search โ€” O(log n)
int binarySearch(int arr[], int low, int high, int target) {
    if (low > high) return -1;        // Base case: not found
    int mid = low + (high - low) / 2;
    if (arr[mid] == target) return mid; // Found!
    if (arr[mid] > target)
        return binarySearch(arr, low, mid - 1, target);
    else
        return binarySearch(arr, mid + 1, high, target);
}
Python
# Recursive Binary Search
def binary_search(arr, low, high, target):
    if low > high:
        return -1
    mid = (low + high) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] > target:
        return binary_search(arr, low, mid - 1, target)
    else:
        return binary_search(arr, mid + 1, high, target)

5. Direct vs Indirect Recursion

Direct Recursion: A function calls itself directly. This is the most common form โ€” factorial, fibonacci, binary search all use direct recursion.

Indirect Recursion: Function A calls function B, and function B calls function A back. The recursion happens through a chain of two or more functions.

Direct Recursion Example

C++
// Direct: function calls itself
void countdown(int n) {
    if (n == 0) {
        cout << "Go!" << endl;
        return;
    }
    cout << n << "... ";
    countdown(n - 1);  // Calls ITSELF
}

Indirect Recursion Example

C++
// Indirect: A calls B, B calls A
void funcB(int n);  // Forward declaration

void funcA(int n) {
    if (n <= 0) return;
    cout << n << " ";
    funcB(n - 1);   // A calls B
}

void funcB(int n) {
    if (n <= 1) return;
    cout << n << " ";
    funcA(n / 2);   // B calls A
}
// funcA(10) โ†’ prints: 10 9 4 3 1
PropertyDirect RecursionIndirect Recursion
Call patternA โ†’ A โ†’ A โ†’ ...A โ†’ B โ†’ A โ†’ B โ†’ ...
ReadabilityEasy to traceHarder to trace
Common usageMost recursive problemsEven/odd checks, mutual parsers, state machines
DebuggingStraightforwardRequires tracing multiple functions
Stack behaviourFrames of one functionFrames of multiple functions interleaved

6. Tail vs Non-Tail Recursion

Tail Recursion: A recursive call is in tail position when it is the very last operation in the function โ€” nothing is done after the recursive call returns. Compilers can optimise tail-recursive functions into loops (called Tail-Call Optimisation / TCO), eliminating stack frame overhead.

Non-Tail Recursion: After the recursive call returns, there's still work to do (like multiplication in factorial). Each call must keep its stack frame alive until the child returns.

Comparison: Non-Tail vs Tail Factorial

C++
// NON-TAIL Recursive Factorial
// After factorial(n-1) returns, we still multiply by n
int factorial_nontail(int n) {
    if (n == 0) return 1;
    return n * factorial_nontail(n - 1);  // โŒ multiplication AFTER call
}

// TAIL Recursive Factorial
// The recursive call is the LAST operation โ€” result is accumulated
int factorial_tail(int n, int acc = 1) {
    if (n == 0) return acc;
    return factorial_tail(n - 1, n * acc);  // โœ… call is LAST operation
}
Python
# Non-tail factorial
def fact_nontail(n):
    if n == 0: return 1
    return n * fact_nontail(n - 1)  # multiply AFTER return

# Tail factorial (Python doesn't optimise this, but conceptually correct)
def fact_tail(n, acc=1):
    if n == 0: return acc
    return fact_tail(n - 1, n * acc)  # call is LAST
FeatureNon-Tail RecursionTail Recursion
Last operationComputation after recursive callRecursive call IS the last operation
Stack framesO(n) frames kept aliveO(1) with TCO (reuses single frame)
Compiler optimisationCannot be optimised to loopCan be optimised to loop (TCO)
Accumulator parameterNot neededRequired to carry partial results
Supported inAll languagesC/C++ (with -O2), Scheme, Scala (not Python/Java)
Python does NOT support tail-call optimisation. Even if you write tail-recursive code in Python, it will still use O(n) stack space. Python's default recursion limit is 1000 (can be changed with sys.setrecursionlimit()). For deep recursion in Python, convert to iteration. C++ with -O2 flag enables TCO.

7. Memory Allocation in Recursion

Every recursive call creates a new stack frame in the program's call stack. Each frame contains:

  • Local variables of the function
  • Function parameters
  • Return address (where to go back after the function completes)
  • Saved registers

Stack Frame Diagram for factorial(3)

HIGH MEMORY โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ factorial(0) โ”‚ โ† Stack Pointer (TOP) โ”‚ n = 0, return addr = ... โ”‚ โ”‚ Returns: 1 โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ factorial(1) โ”‚ โ”‚ n = 1, return addr = ... โ”‚ โ”‚ Waiting for factorial(0) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ factorial(2) โ”‚ โ”‚ n = 2, return addr = ... โ”‚ โ”‚ Waiting for factorial(1) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ factorial(3) โ”‚ โ”‚ n = 3, return addr = ... โ”‚ โ”‚ Waiting for factorial(2) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ main() โ”‚ โ† Stack Bottom โ”‚ Waiting for factorial(3) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ LOW MEMORY Each frame: ~32โ€“64 bytes Stack space: O(n) for recursion depth n Default stack size: ~1 MB (Linux) / 1 MB (Windows) Max depth: ~10,000โ€“30,000 calls before stack overflow

Why Stack Overflow Happens: If n = 1,000,000 and you call factorial(1000000), the program needs 1 million stack frames ร— ~50 bytes each = ~50 MB. But the stack is only ~1 MB. Result: Stack Overflow โ€” the program crashes.

For very deep recursion (n > 10,000): Either convert to iteration, use tail recursion (in C++ with -O2), or increase the stack size. In C++: compile with -Wl,-z,stacksize=67108864 for 64 MB stack. In Python: import sys; sys.setrecursionlimit(100000) and use import threading; threading.stack_size(67108864).

8. Advantages & Disadvantages โ€” Recursion vs Iteration

ParameterRecursionIteration
Code Readabilityโœ… Clean, elegant, mirrors mathematical definitionโš ๏ธ Can be verbose with explicit stacks
Time Complexityโš ๏ธ Can be exponential without memoization (e.g., O(2โฟ) for naive Fibonacci)โœ… Usually linear โ€” no repeated subproblems
Space ComplexityโŒ O(n) stack space for depth nโœ… O(1) โ€” no extra stack frames
Debugging Easeโš ๏ธ Hard to trace deep recursion; stack traces can be longโœ… Easy to step through with debugger
PerformanceโŒ Function call overhead (push/pop frames)โœ… No overhead โ€” direct loop instructions
Problem Suitabilityโœ… Trees, graphs, backtracking, divide-and-conquerโœ… Simple loops, array traversals, counting
Interview wisdom: Always be able to write BOTH recursive and iterative solutions. Start with recursion (easier to think about), then mention: "I can convert this to iteration for better space efficiency." This shows depth of understanding.

9. Backtracking

Backtracking is a refined form of recursion where you build a solution incrementally, one piece at a time, and abandon ("backtrack") a path as soon as you determine it cannot lead to a valid solution. Think of it as exploring a maze: walk forward, hit a dead end, go back to the last junction, try another path.

The N-Queens Problem

Problem: Place N queens on an Nร—N chessboard such that no two queens threaten each other (no two in the same row, column, or diagonal).

Complete Board Trace for 4-Queens

Step 1: Place Q1 at (0,0) Step 2: Try Q2 in row 1 Q . . . Q . . . . . . . . . Q . โ† (1,2) is safe . . . . . . . . . . . . . . . . Step 3: Try Q3 in row 2 Step 4: No safe spot in row 3! Q . . . Q . . . BACKTRACK! . . Q . . . Q . Remove Q3, try next . . . . โ† No safe col! . . . . position for Q3 . . . . . . . . Step 5: Backtrack Q2 to (1,3) Step 6: Place Q3 at (2,1) Q . . . Q . . . . . . Q . . . Q . . . . . Q . . โ† (2,1) is safe . . . . . . . . Step 7: No safe spot for Q4! Step 8: Backtrack to Q1 at (0,1) Q . . . . Q . . . . . Q BACKTRACK! . . . . . Q . . all the way . . . . . . . . to Q1 . . . . Step 9: Place queens SOLUTION FOUND! โœ… . Q . . . Q . . . . . Q . . . Q Q . . . Q . . . . . Q . . . Q .

N-Queens Code

C++
#include <iostream>
#include <vector>
using namespace std;

bool isSafe(vector<vector<int>> &board, int row, int col, int n) {
    // Check column above
    for (int i = 0; i < row; i++)
        if (board[i][col]) return false;
    // Check upper-left diagonal
    for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--)
        if (board[i][j]) return false;
    // Check upper-right diagonal
    for (int i=row-1, j=col+1; i>=0 && j<n; i--, j++)
        if (board[i][j]) return false;
    return true;
}

bool solveNQueens(vector<vector<int>> &board, int row, int n) {
    if (row == n) return true;  // All queens placed!
    for (int col = 0; col < n; col++) {
        if (isSafe(board, row, col, n)) {
            board[row][col] = 1;           // Place queen
            if (solveNQueens(board, row+1, n))
                return true;
            board[row][col] = 0;           // BACKTRACK: remove queen
        }
    }
    return false;  // No valid position in this row
}

int main() {
    int n = 4;
    vector<vector<int>> board(n, vector<int>(n, 0));
    if (solveNQueens(board, 0, n)) {
        for (auto &row : board) {
            for (int cell : row)
                cout << (cell ? "Q " : ". ");
            cout << endl;
        }
    }
    return 0;
}
. Q . . . . . Q Q . . . . . Q .

Sudoku Solver Concept

A Sudoku solver uses the same backtracking pattern: find an empty cell, try digits 1โ€“9, check validity (row, column, 3ร—3 box), recurse to next cell. If no digit works, backtrack to the previous cell and try the next digit. The recursion tree can have up to 9โธยน nodes, but constraint checking prunes most branches early.

Subset Sum Problem

Problem: Given a set of integers and a target sum, find if any subset sums to the target.

C++
bool subsetSum(vector<int> &arr, int idx, int target) {
    if (target == 0) return true;       // Found a valid subset!
    if (idx < 0 || target < 0) return false;
    // Include arr[idx] OR exclude it
    return subsetSum(arr, idx-1, target-arr[idx])  // Include
        || subsetSum(arr, idx-1, target);          // Exclude
}

Generating All Permutations

C++
void permute(vector<int> &arr, int start, int end) {
    if (start == end) {
        for (int x : arr) cout << x << " ";
        cout << endl;
        return;
    }
    for (int i = start; i <= end; i++) {
        swap(arr[start], arr[i]);        // Choose
        permute(arr, start + 1, end);    // Explore
        swap(arr[start], arr[i]);        // Un-choose (backtrack)
    }
}

Recursion Tree for Permutations of {1, 2, 3}

permute({1,2,3}, 0) / | \ swap(0,0) swap(0,1) swap(0,2) {1,2,3} {2,1,3} {3,2,1} / \ / \ / \ swap(1,1) swap(1,2) swap(1,1) swap(1,2) swap(1,1) swap(1,2) {1,2,3} {1,3,2} {2,1,3} {2,3,1} {3,2,1} {3,1,2} โ†“ โ†“ โ†“ โ†“ โ†“ โ†“ PRINT PRINT PRINT PRINT PRINT PRINT Output: 1 2 3 | 1 3 2 | 2 1 3 | 2 3 1 | 3 2 1 | 3 1 2 Total permutations: 3! = 6

10. Memoization โ€” Caching Recursive Results

The Problem: Look at the Fibonacci recursion tree above. fib(3) is computed 2 times. fib(2) is computed 3 times. For fib(50), there are over 2โตโฐ โ‰ˆ 10ยนโต redundant calls. This is catastrophically slow.

The Solution โ€” Memoization: Store the result of each subproblem the first time it's computed. If the same subproblem is encountered again, return the cached result instead of recomputing. This is called memoization (from "memo" โ€” a note to yourself).

Fibonacci Without vs With Memoization

WITHOUT MEMOIZATION: WITH MEMOIZATION: fib(5) fib(5) โ”œโ”€โ”€ fib(4) โ”œโ”€โ”€ fib(4) โ”‚ โ”œโ”€โ”€ fib(3) โ”‚ โ”œโ”€โ”€ fib(3) โ”‚ โ”‚ โ”œโ”€โ”€ fib(2) โ”‚ โ”‚ โ”œโ”€โ”€ fib(2) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ fib(1) โ†’ 1 โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ fib(1) โ†’ 1 โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ fib(0) โ†’ 0 โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ fib(0) โ†’ 0 โ”‚ โ”‚ โ””โ”€โ”€ fib(1) โ†’ 1 โ”‚ โ”‚ โ””โ”€โ”€ fib(1) โ†’ 1 [cached] โ”‚ โ””โ”€โ”€ fib(2) โ† RECOMPUTED! โ”‚ โ””โ”€โ”€ fib(2) โ†’ 1 [CACHED! โœ…] โ”‚ โ”œโ”€โ”€ fib(1) โ†’ 1 โ””โ”€โ”€ fib(3) โ†’ 2 [CACHED! โœ…] โ”‚ โ””โ”€โ”€ fib(0) โ†’ 0 โ””โ”€โ”€ fib(3) โ† RECOMPUTED! Calls: 9 (linear) โ”œโ”€โ”€ fib(2) โ† RECOMPUTED! Time: O(n) โ”‚ โ”œโ”€โ”€ fib(1) โ†’ 1 โ”‚ โ””โ”€โ”€ fib(0) โ†’ 0 โ””โ”€โ”€ fib(1) โ†’ 1 Calls: 15 (exponential) Time: O(2โฟ)
C++
#include <iostream>
#include <unordered_map>
using namespace std;

unordered_map<int, long long> memo;

long long fib_memo(int n) {
    if (n <= 0) return 0;
    if (n == 1) return 1;
    if (memo.count(n)) return memo[n];  // Return cached result
    memo[n] = fib_memo(n-1) + fib_memo(n-2);  // Compute & store
    return memo[n];
}

int main() {
    cout << "fib(50) = " << fib_memo(50) << endl;
    return 0;
}
Python
# Memoized Fibonacci using dictionary
def fib_memo(n, memo={}):
    if n <= 0: return 0
    if n == 1: return 1
    if n in memo: return memo[n]
    memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo)
    return memo[n]

print(f"fib(50) = {fib_memo(50)}")
fib(50) = 12586269025
MetricNaive FibonacciMemoized Fibonacci
Time ComplexityO(2โฟ)O(n)
Space ComplexityO(n) stackO(n) stack + O(n) cache
fib(40) time~55 seconds~0.00001 seconds
fib(50) feasibilityโŒ Takes hoursโœ… Instant
Memoization bridges to Dynamic Programming (DP). Memoization is "top-down DP" โ€” you start from the big problem and cache as you go down. Tabulation is "bottom-up DP" โ€” you start from base cases and build up. Unit 4 will deep-dive into DP. Master memoization here, and DP will feel natural.

11. Classic Recursive Problems

Problem 1: Next Happy Number

๐Ÿ”ข Next Happy Number

PROBLEM STATEMENT

A happy number is a number where repeatedly summing the squares of its digits eventually reaches 1. Example: 19 โ†’ 1ยฒ+9ยฒ = 82 โ†’ 8ยฒ+2ยฒ = 68 โ†’ 6ยฒ+8ยฒ = 100 โ†’ 1ยฒ+0ยฒ+0ยฒ = 1 โœ…. Given a number N, find the next happy number after N.

APPROACH

1. For each candidate starting from N+1, check if it's happy. 2. To check happiness: recursively sum squares of digits. Use a set to detect cycles (if we see a number again, it's not happy). 3. Return the first happy number found.

C++
#include <iostream>
#include <unordered_set>
using namespace std;

int sumOfSquares(int n) {
    if (n == 0) return 0;
    int digit = n % 10;
    return digit * digit + sumOfSquares(n / 10);
}

bool isHappy(int n, unordered_set<int> &seen) {
    if (n == 1) return true;
    if (seen.count(n)) return false;  // Cycle detected
    seen.insert(n);
    return isHappy(sumOfSquares(n), seen);
}

int nextHappy(int n) {
    int candidate = n + 1;
    while (true) {
        unordered_set<int> seen;
        if (isHappy(candidate, seen)) return candidate;
        candidate++;
    }
}

int main() {
    cout << "Next happy after 6: " << nextHappy(6) << endl;
    cout << "Next happy after 19: " << nextHappy(19) << endl;
    return 0;
}
Next happy after 6: 7 Next happy after 19: 23

Complexity: Time: O(k ร— log n) per candidate, where k is the cycle length. Space: O(log n) for the set.

Problem 2: Sum String

๐Ÿ”ค Sum String

PROBLEM STATEMENT

A string is called a sum-string if the rightmost substring can be written as the sum of the two preceding substrings, and this property holds recursively. Example: "12358132134" โ†’ 1, 2, 3, 5, 8, 13, 21, 34 (each number is sum of two previous). Given a numeric string, check if it is a sum string.

APPROACH

1. Try all possible lengths for the first two numbers. 2. For each pair, compute their sum and check if the string starts with that sum. 3. If yes, recursively check the remaining string with the second number and the sum as the new pair. 4. Base case: if we've consumed the entire string, return true.

C++
#include <iostream>
#include <string>
using namespace std;

string addStrings(string a, string b) {
    string result;
    int carry = 0, i = a.size()-1, j = b.size()-1;
    while (i >= 0 || j >= 0 || carry) {
        int sum = carry;
        if (i >= 0) sum += a[i--] - '0';
        if (j >= 0) sum += b[j--] - '0';
        result = char(sum % 10 + '0') + result;
        carry = sum / 10;
    }
    return result;
}

bool checkSum(string s, int len1, int len2) {
    string s1 = s.substr(0, len1);
    string s2 = s.substr(len1, len2);
    string sum = addStrings(s1, s2);
    int sumLen = sum.size();
    if (len1 + len2 + sumLen > s.size()) return false;
    if (s.substr(len1 + len2, sumLen) != sum) return false;
    if (len1 + len2 + sumLen == s.size()) return true;
    return checkSum(s.substr(len1), len2, sumLen);
}

bool isSumString(string s) {
    int n = s.size();
    for (int i = 1; i < n; i++)
        for (int j = 1; i+j < n; j++)
            if (checkSum(s, i, j)) return true;
    return false;
}

int main() {
    cout << isSumString("12358132134") << endl;  // 1
    cout << isSumString("1111112223") << endl;   // 1
    return 0;
}
1 1

Complexity: Time: O(nยณ) in worst case. Space: O(n) for string operations.

Problem 3: Water Overflow (Champagne Tower)

๐Ÿฅ‚ Water Overflow โ€” Champagne Tower Problem

PROBLEM STATEMENT

Glasses are arranged in a Pascal triangle formation. K litres of water are poured into the topmost glass. When a glass overflows, excess water splits equally to the two glasses directly below it. Given K litres poured, find how much water is in the j-th glass of the i-th row (0-indexed).

APPROACH

Simulate the pouring process. Use a 2D array. Pour K litres into glass[0][0]. For each glass, if it has more than 1 litre, the excess overflows equally to the two glasses below. Process row by row.

C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

double champagneTower(int poured, int query_row, int query_glass) {
    vector<vector<double>> tower(101, vector<double>(101, 0.0));
    tower[0][0] = (double)poured;

    for (int row = 0; row <= query_row; row++) {
        for (int col = 0; col <= row; col++) {
            double overflow = (tower[row][col] - 1.0) / 2.0;
            if (overflow > 0) {
                tower[row+1][col]   += overflow;
                tower[row+1][col+1] += overflow;
            }
        }
    }
    return min(1.0, tower[query_row][query_glass]);
}

int main() {
    cout << "Row 2, Glass 1 (pour 4): " << champagneTower(4, 2, 1) << endl;
    cout << "Row 1, Glass 0 (pour 2): " << champagneTower(2, 1, 0) << endl;
    return 0;
}
Row 2, Glass 1 (pour 4): 1 Row 1, Glass 0 (pour 2): 0.5

Complexity: Time: O(rowยฒ). Space: O(rowยฒ) for the 2D array.

Section D

Learn by Doing โ€” 3-Tier Lab Structure

๐ŸŸข Tier 1 โ€” GUIDED TASK: Factorial & Fibonacci โ€” Your First Recursive Programs

โฑ๏ธ 45โ€“60 minutesBeginnerZero prior knowledge assumed

Step 1: Set Up Your Environment

Open your C++ IDE (VS Code, CodeBlocks, or online: ideone.com / repl.it). Create a new file recursion_basics.cpp.

Step 2: Write Recursive Factorial

Type the factorial function from Section C. Add print statements to trace each call:

C++
#include <iostream>
using namespace std;

int factorial(int n) {
    cout << "Called factorial(" << n << ")" << endl;
    if (n == 0) {
        cout << "  Base case! Returning 1" << endl;
        return 1;
    }
    int result = n * factorial(n - 1);
    cout << "  factorial(" << n << ") = " << result << endl;
    return result;
}

int main() {
    cout << "Answer: " << factorial(5) << endl;
    return 0;
}

Step 3: Run and Observe the Trace

Compile and run. You should see the cascade of calls going down (push), then results coming back up (pop). Draw this on paper โ€” draw boxes stacked on top of each other for each call.

Step 4: Write Recursive Fibonacci with Trace

C++
int fib(int n, int depth = 0) {
    for(int i=0;i<depth;i++) cout << "  ";  // Indent for depth
    cout << "fib(" << n << ")" << endl;
    if (n <= 0) return 0;
    if (n == 1) return 1;
    return fib(n-1, depth+1) + fib(n-2, depth+1);
}

Step 5: Count the Calls

Run fib(5) and count how many times each subproblem is computed. Verify it matches the recursion tree diagram from Section C. Then add memoization and observe the dramatic reduction in calls.

๐ŸŽ‰ Congratulations! You've implemented and traced your first recursive programs. You now understand the call stack intuitively.

๐ŸŸก Tier 2 โ€” SEMI-GUIDED TASK: Solve the N-Queens Problem

โฑ๏ธ 90 minutesIntermediateHints provided, you fill the gaps

Your Mission:

Implement the N-Queens solver for N=8. Print all 92 solutions, with each solution showing the board configuration.

Hints:

  1. Data Structure: Use a 1D array queen[i] = col where row i has queen at column col. This automatically ensures one queen per row.
  2. Safety Check: Two queens at (r1, c1) and (r2, c2) attack each other if: c1 == c2 (same column) OR |r1-r2| == |c1-c2| (same diagonal).
  3. Backtracking Pattern: For each row, try every column. If safe, place and recurse to next row. If stuck, backtrack.
  4. Counting: Use a global counter. Increment when a complete solution is found (row == N).
  5. Print Board: For each solution, print the board with 'Q' and '.' characters.
Stretch Goal: Measure execution time for N=8, N=10, N=12, and N=14. Plot the growth. Does it match the expected exponential behaviour?

๐Ÿ”ด Tier 3 โ€” OPEN CHALLENGE: Build a Complete Sudoku Solver

โฑ๏ธ 2โ€“3 hoursAdvancedNo instructions โ€” real competition-level problem

The Brief:

Build a Sudoku solver that takes a 9ร—9 grid (0 = empty) and fills in all missing numbers using backtracking. Your solver must:

  1. Find the next empty cell (left to right, top to bottom)
  2. Try digits 1โ€“9 in that cell
  3. Check validity: no duplicate in same row, column, or 3ร—3 sub-box
  4. If valid, recurse to next empty cell
  5. If no digit works, backtrack (set cell back to 0)
  6. Print the completed grid when solved

Test with this puzzle:

Input
5 3 0 | 0 7 0 | 0 0 0
6 0 0 | 1 9 5 | 0 0 0
0 9 8 | 0 0 0 | 0 6 0
------+-------+------
8 0 0 | 0 6 0 | 0 0 3
4 0 0 | 8 0 3 | 0 0 1
7 0 0 | 0 2 0 | 0 0 6
------+-------+------
0 6 0 | 0 0 0 | 2 8 0
0 0 0 | 4 1 9 | 0 0 5
0 0 0 | 0 8 0 | 0 7 9
A working Sudoku solver is an impressive portfolio piece. Add it to your GitHub with a clean README, input/output examples, and time complexity analysis. Mention "backtracking algorithm" and "constraint satisfaction" in your resume. Interviewers at Amazon, Google, and Microsoft LOVE seeing this.
Section E

Problem Set โ€” Practice Problems

Tracing Problems โ€” Draw the Recursion Tree (5)

#ProblemDifficulty
T1Trace factorial(6): Draw the complete call stack at maximum depth. Show each frame's local variable n and return value.Beginner
T2Draw the full recursion tree for fib(6). Count total nodes. Identify which subproblems are computed more than once.Beginner
T3Trace power_fast(2, 10). Show each recursive call, the value of half, and whether n is even or odd at each step.Intermediate
T4Draw the backtracking tree for placing 4 queens on a 4ร—4 board. Show all attempted placements and backtracks until the first solution.Intermediate
T5Trace permute({1,2,3}, 0, 2). Draw the complete recursion tree showing all swaps and the final permutation at each leaf node.Advanced

Programming Problems (8)

#ProblemDifficulty
P1Reverse a String Recursively: Write a function that reverses a string using recursion (no loops, no built-in reverse). Test with "Hello" โ†’ "olleH".Beginner
P2Tower of Hanoi: Implement the classic Tower of Hanoi for n disks. Print each move. Verify that n disks require 2โฟ - 1 moves.Beginner
P3Check Palindrome Recursively: Write a recursive function to check if a string is a palindrome. Handle both even and odd length strings.Beginner
P4Generate All Subsets: Given a set of n distinct integers, generate all 2โฟ subsets using recursion (include/exclude pattern).Intermediate
P5Rat in a Maze: Given an nร—n grid with blocked cells (0) and open cells (1), find a path from (0,0) to (n-1,n-1) using backtracking. Print the path.Intermediate
P6Word Search: Given a 2D grid of characters and a word, check if the word exists in the grid by traversing adjacent cells (up/down/left/right). Each cell used at most once.Intermediate
P7Combination Sum: Given an array of distinct integers and a target, find all unique combinations that sum to target. Each number may be used unlimited times.Advanced
P8Regular Expression Matching: Implement a simplified regex matcher supporting '.' (any character) and '*' (zero or more of preceding) using recursion.Advanced

Industry Application Problems (3)

#ProblemDifficulty
I1File System Size Calculator: Write a recursive function that calculates the total size of a directory and all its subdirectories (simulated with a tree data structure).Intermediate
I2JSON Parser (Nested Objects): Write a recursive descent parser that validates and pretty-prints nested JSON objects up to arbitrary depth.Advanced
I3Network Packet Router: Simulate a recursive DFS-based packet routing algorithm that finds a path from source to destination in a network graph with 20 nodes.Advanced

Interview Problems (3)

#ProblemCompanyDifficulty
V1Letter Combinations of a Phone Number: Given digits 2-9, return all possible letter combinations (like T9 keyboard). [LeetCode #17]Amazon, GoogleIntermediate
V2Generate Parentheses: Given n, generate all valid combinations of n pairs of parentheses. [LeetCode #22]Microsoft, FacebookIntermediate
V3Decode Ways: Given a string of digits, count the number of ways to decode it (A=1, B=2, ..., Z=26). Use memoization. [LeetCode #91]Amazon, FlipkartAdvanced
Section F

MCQ Assessment Bank โ€” 30 Questions (Bloom's Mapped)

Remember / Identify (Q1โ€“Q5)

Q1

A recursive function must have:

  1. Only a base case
  2. Only a recursive case
  3. Both a base case and a recursive case
  4. Neither โ€” recursion works automatically
Remember
โœ… Answer: (C) โ€” Every recursive function needs a base case (to stop) and a recursive case (to make progress toward the base case).
Q2

What is the base case for the recursive factorial function factorial(n)?

  1. n == 1
  2. n == 0
  3. n < 0
  4. n == n-1
Remember
โœ… Answer: (B) โ€” The mathematical definition states 0! = 1. This is the standard base case, though n == 1 also works as an alternate base case.
Q3

In tail recursion, the recursive call is:

  1. The first statement in the function
  2. Inside a loop
  3. The last operation before the function returns
  4. Made to a different function
Remember
โœ… Answer: (C) โ€” In tail recursion, the recursive call is the very last thing the function does. No computation happens after the recursive call returns.
Q4

When function A calls function B and function B calls function A, this is called:

  1. Direct recursion
  2. Indirect recursion
  3. Tail recursion
  4. Mutual exclusion
Remember
โœ… Answer: (B) โ€” Indirect recursion involves two or more functions calling each other in a cycle (Aโ†’Bโ†’Aโ†’B...).
Q5

The space complexity of a recursive function with depth n is typically:

  1. O(1)
  2. O(log n)
  3. O(n)
  4. O(nยฒ)
Remember
โœ… Answer: (C) โ€” Each recursive call creates a stack frame. For depth n, there are n frames on the stack simultaneously, giving O(n) space.

Understand / Explain (Q6โ€“Q10)

Q6

Why does the naive recursive Fibonacci have O(2โฟ) time complexity?

  1. Because it uses a loop of size 2โฟ
  2. Because each call makes two more calls, creating a binary tree of calls
  3. Because the base case is reached after 2โฟ steps
  4. Because it uses an array of size 2โฟ
Understand
โœ… Answer: (B) โ€” Each call to fib(n) spawns two child calls: fib(n-1) and fib(n-2). This creates a binary tree of calls with height n, resulting in approximately 2โฟ total calls.
Q7

What happens when a recursive function has no base case?

  1. It returns 0
  2. It runs once and stops
  3. It calls itself infinitely until the stack overflows
  4. The compiler automatically adds a base case
Understand
โœ… Answer: (C) โ€” Without a base case, the function never stops calling itself. Each call adds a frame to the stack until memory is exhausted, causing a stack overflow (segmentation fault).
Q8

How does memoization improve the Fibonacci function?

  1. It converts recursion to iteration
  2. It stores already-computed results to avoid redundant calculations
  3. It reduces the number of base cases
  4. It removes the recursive calls entirely
Understand
โœ… Answer: (B) โ€” Memoization caches the result of each unique subproblem. When fib(k) is needed again, the cached value is returned in O(1) instead of recomputing the entire subtree.
Q9

In the N-Queens problem, "backtracking" means:

  1. Removing all queens and starting over
  2. Undoing the last placement and trying the next option when a conflict is detected
  3. Sorting the queens by column
  4. Using dynamic programming to find the optimal placement
Understand
โœ… Answer: (B) โ€” Backtracking means when the current partial solution can't lead to a valid complete solution, undo the last decision (remove the queen) and try the next alternative.
Q10

Why can tail-recursive functions be optimised by compilers but non-tail-recursive functions cannot?

  1. Tail-recursive functions use less memory
  2. The compiler can reuse the current stack frame since no work remains after the recursive call
  3. Tail-recursive functions are always faster
  4. Non-tail-recursive functions have bugs
Understand
โœ… Answer: (B) โ€” In tail recursion, the recursive call is the last operation. Since nothing needs to be done after it returns, the compiler can replace the current frame instead of creating a new one, effectively converting recursion into a loop.

Apply / Implement (Q11โ€“Q15)

Q11

What is the output of the following code?

int f(int n) {
    if (n <= 1) return n;
    return f(n-1) + f(n-2);
}
cout << f(7);
  1. 7
  2. 13
  3. 21
  4. 8
Apply
โœ… Answer: (B) โ€” This is the Fibonacci function. fib(7) = fib(6) + fib(5) = 8 + 5 = 13. The sequence: 0, 1, 1, 2, 3, 5, 8, 13.
Q12

What does this recursive function compute?

int mystery(int n) {
    if (n == 0) return 0;
    return (n % 10) + mystery(n / 10);
}
  1. Number of digits in n
  2. Sum of digits of n
  3. Reverse of n
  4. Product of digits of n
Apply
โœ… Answer: (B) โ€” The function takes the last digit (n % 10) and adds it to the sum of digits of the remaining number (n / 10). This recursively sums all digits.
Q13

To convert the non-tail factorial to tail-recursive form, you need to add:

  1. A loop
  2. An accumulator parameter
  3. A global variable
  4. A second base case
Apply
โœ… Answer: (B) โ€” Tail recursion requires carrying the partial result forward. An accumulator parameter (e.g., factorial(n, acc)) stores the running product, making the recursive call the last operation.
Q14

In a recursive binary search on a sorted array of 1024 elements, the maximum number of recursive calls is:

  1. 1024
  2. 512
  3. 10
  4. 11
Apply
โœ… Answer: (D) โ€” Binary search halves the array each time: logโ‚‚(1024) = 10 comparisons, plus one final call that returns -1 = 11 calls maximum.
Q15

What is the output of power_fast(3, 4) using the optimised O(log n) approach?

  1. 12
  2. 27
  3. 81
  4. 64
Apply
โœ… Answer: (C) โ€” 3โด = 81. The function computes: half = power(3,2) = 9, then 9 ร— 9 = 81 (since 4 is even).

Analyze / Compare (Q16โ€“Q20)

Q16

How many times is fib(2) computed in the naive recursive call to fib(6)?

  1. 3
  2. 5
  3. 8
  4. 13
Analyze
โœ… Answer: (B) โ€” Drawing the full recursion tree for fib(6), fib(2) appears 5 times as a subproblem. This redundancy is exactly what memoization eliminates.
Q17

Comparing recursion and iteration for computing factorial of n, which statement is TRUE?

  1. Recursion is always faster
  2. Iteration uses O(n) extra space
  3. Recursion uses O(n) stack space while iteration uses O(1)
  4. Both have the same space complexity
Analyze
โœ… Answer: (C) โ€” Recursive factorial creates n stack frames = O(n) space. Iterative factorial uses a single loop variable = O(1) space. Both are O(n) time.
Q18

The N-Queens problem for an 8ร—8 board has 92 solutions. What type of algorithm is best suited to find ALL solutions?

  1. Greedy
  2. Dynamic Programming
  3. Backtracking
  4. Binary Search
Analyze
โœ… Answer: (C) โ€” Backtracking systematically explores all possibilities, pruning invalid branches early. It's the standard approach for constraint satisfaction problems like N-Queens.
Q19

What is the time complexity of the subset-sum problem solved via naive recursion on an array of n elements?

  1. O(n)
  2. O(nยฒ)
  3. O(2โฟ)
  4. O(n!)
Analyze
โœ… Answer: (C) โ€” For each element, we have two choices: include or exclude. With n elements, this creates 2โฟ possible subsets to explore.
Q20

Memoized Fibonacci uses O(n) extra space for caching. Compared to the bottom-up tabulation approach:

  1. Memoization is always more space-efficient
  2. Both use O(n) space, but tabulation can be optimised to O(1) space
  3. Tabulation uses O(nยฒ) space
  4. Memoization uses O(1) space
Analyze
โœ… Answer: (B) โ€” Both use O(n) space by default. But the bottom-up approach only needs the last 2 values at any time, so it can be optimised to O(1) space. Memoization cannot do this because it relies on the call stack.

Evaluate / Judge (Q21โ€“Q25)

Q21

A student says: "Recursion is always better than iteration because it's more elegant." What is wrong with this claim?

  1. Nothing โ€” recursion is always better
  2. Recursion can be slower due to function call overhead and uses more stack space
  3. Iteration cannot solve the same problems as recursion
  4. Recursion doesn't work in C++
Evaluate
โœ… Answer: (B) โ€” While recursion is elegant, it has real costs: function call overhead (pushing/popping frames), O(n) stack space, and risk of stack overflow. For simple linear problems like factorial, iteration is more efficient.
Q22

For which of these problems is recursion clearly the BEST approach?

  1. Finding the maximum in an array
  2. Traversing a tree data structure
  3. Summing numbers 1 to n
  4. Printing "Hello" 10 times
Evaluate
โœ… Answer: (B) โ€” Trees are inherently recursive structures (each subtree is itself a tree). Tree traversal (inorder, preorder, postorder) is naturally and elegantly expressed with recursion. The other problems are simpler with loops.
Q23

A recursive Fibonacci function works correctly for fib(30) but crashes for fib(100000). Which is the best fix?

  1. Increase the recursion limit to infinity
  2. Add more base cases
  3. Use memoization or convert to iterative bottom-up approach
  4. Use a faster computer
Evaluate
โœ… Answer: (C) โ€” The crash is due to stack overflow (too many frames) and exponential time. Memoization fixes the time issue. Converting to iteration fixes the stack issue. A bottom-up iterative approach with O(1) space is the best solution for large n.
Q24

In a coding interview, you solve a problem with recursion but the interviewer asks about the space complexity. Your recursive solution uses O(n) stack space. What should you suggest?

  1. "O(n) is the best possible"
  2. "I can convert this to tail recursion for O(1) space with TCO, or rewrite iteratively"
  3. "Space complexity doesn't matter"
  4. "I'll use a bigger computer"
Evaluate
โœ… Answer: (B) โ€” Acknowledging the space trade-off and proposing alternatives (tail recursion, iteration) shows deep understanding. Interviewers value candidates who can discuss trade-offs.
Q25

Which approach is better for the N-Queens problem: brute force (try all N^N placements) or backtracking?

  1. Brute force โ€” it's simpler to implement
  2. Backtracking โ€” it prunes invalid branches early, drastically reducing the search space
  3. Both are equally efficient
  4. Neither โ€” use dynamic programming instead
Evaluate
โœ… Answer: (B) โ€” Brute force checks N^N = 16,777,216 placements for N=8. Backtracking explores only ~15,720 nodes (pruning 99.9% of possibilities). This makes backtracking vastly superior for constraint-satisfaction problems.

Create / Design (Q26โ€“Q30)

Q26

To generate all permutations of n elements using recursion, the correct approach is:

  1. Sort the elements first, then print
  2. For each position, swap with remaining elements, recurse, then swap back (backtrack)
  3. Use nested loops of depth n
  4. Use binary search on each element
Create
โœ… Answer: (B) โ€” The swap-recurse-unswap pattern is the standard backtracking approach for generating permutations. Each recursive call fixes one position and explores all choices for the remaining positions.
Q27

To add memoization to a recursive function, you need to:

  1. Add a loop inside the function
  2. Create a cache (array/map), check it before computing, and store results after computing
  3. Remove the base case
  4. Call the function from main() multiple times
Create
โœ… Answer: (B) โ€” The memoization pattern: (1) Check if result is in cache โ†’ return it. (2) If not, compute recursively. (3) Store the result in cache before returning.
Q28

To design a recursive Sudoku solver, the correct recursive structure is:

  1. Try all 9โธยน combinations at once
  2. Find empty cell โ†’ try digits 1-9 โ†’ check constraints โ†’ recurse to next cell โ†’ backtrack if stuck
  3. Fill row by row without checking constraints
  4. Use sorting to place numbers
Create
โœ… Answer: (B) โ€” The standard Sudoku solver uses backtracking: find an empty cell, try each digit, validate constraints (row, column, box), recurse. If no valid digit exists, backtrack to the previous cell.
Q29

To convert the following non-tail recursive function to tail-recursive form, the accumulator should track:

int sum(int n) {
    if (n == 0) return 0;
    return n + sum(n-1);
}
  1. The value of n
  2. The running total of the sum computed so far
  3. The number of recursive calls made
  4. The base case value
Create
โœ… Answer: (B) โ€” The tail-recursive version: sum(n, acc=0) { if (n==0) return acc; return sum(n-1, acc+n); }. The accumulator carries the running total forward.
Q30

You're designing a solution for the "generate all valid parentheses" problem. The recursive function should track:

  1. Only the number of opening brackets
  2. The count of opening and closing brackets used so far, and the current string
  3. Just the total length of the string
  4. A counter for the number of solutions
Create
โœ… Answer: (B) โ€” The function needs: open_count (number of '(' used), close_count (number of ')' used), and the current string being built. Constraints: open_count โ‰ค n, close_count โ‰ค open_count.
Section G

Short Answer Questions (8)

Q1. Define recursion and state its two essential components.

Answer: Recursion is a programming technique where a function calls itself to solve smaller instances of the same problem. The two essential components are: (1) Base case โ€” the condition that stops recursion and returns a known value directly, and (2) Recursive case โ€” the function calls itself with modified (smaller) arguments, making progress toward the base case.

Q2. What is the difference between direct and indirect recursion? Give one example of each.

Answer: In direct recursion, a function calls itself (e.g., factorial(n) calls factorial(n-1)). In indirect recursion, function A calls function B, and function B calls function A (e.g., isEven(n) calls isOdd(n-1), and isOdd(n) calls isEven(n-1)).

Q3. Why does naive recursive Fibonacci have exponential time complexity?

Answer: Each call to fib(n) makes two recursive calls: fib(n-1) and fib(n-2). This creates a binary tree of calls where the same subproblems are recomputed multiple times. The total number of calls grows approximately as 2โฟ, giving O(2โฟ) time complexity. For example, fib(5) makes 15 calls, but only 6 are unique subproblems.

Q4. Explain tail-call optimisation (TCO) in one paragraph.

Answer: Tail-Call Optimisation is a compiler technique where a tail-recursive function (one whose last operation is the recursive call) is transformed into an iterative loop. Since no work remains after the recursive call returns, the compiler can reuse the current stack frame instead of creating a new one. This reduces space from O(n) to O(1). TCO is supported in C/C++ (with -O2), Scheme, and Scala, but NOT in Python or Java.

Q5. What is backtracking? How does it differ from brute force?

Answer: Backtracking is a refined recursive technique that builds solutions incrementally and abandons (backtracks from) partial solutions as soon as they violate constraints. Unlike brute force, which tries ALL possible combinations, backtracking prunes invalid branches early. For N-Queens (N=8), brute force checks 8โธ = 16.7 million placements, while backtracking explores only ~15,720 nodes โ€” a 99.9% reduction.

Q6. What is memoization? How does it improve time complexity?

Answer: Memoization is an optimisation technique where the results of expensive recursive calls are cached (stored in a hash map or array). When the same subproblem is encountered again, the cached result is returned in O(1) instead of recomputing. For Fibonacci, memoization reduces time from O(2โฟ) to O(n) because each of the n subproblems is computed exactly once.

Q7. What causes a Stack Overflow error in recursion?

Answer: Each recursive call creates a new stack frame in memory. If the recursion is too deep (no base case, or input too large), stack frames accumulate beyond the available stack memory (typically 1โ€“8 MB). When the stack limit is exceeded, the program crashes with a Stack Overflow error (segmentation fault in C++, RecursionError in Python).

Q8. Give two advantages and two disadvantages of recursion over iteration.

Advantages: (1) Code is cleaner and more readable, especially for tree/graph problems. (2) Naturally expresses divide-and-conquer and backtracking algorithms. Disadvantages: (1) Uses O(n) stack space, risking stack overflow. (2) Function call overhead (pushing/popping frames) makes it slower than equivalent iteration for simple problems.

Section H

Long Answer Questions (3)

Q1. Explain the N-Queens problem and solve it for N=4 using backtracking. Show the complete trace with board states.

Answer Outline:

  1. Problem Definition: Place N queens on an Nร—N chessboard so that no two queens attack each other (no shared row, column, or diagonal).
  2. Why Backtracking: Brute force would check 4โด = 256 placements. Backtracking prunes most branches. For N=4, backtracking explores ~45 nodes vs 256.
  3. Algorithm: Place one queen per row. For each row, try each column. Check safety (column and diagonal conflicts). If safe, recurse to next row. If no column works, backtrack.
  4. Trace for 4-Queens: Start Row 0, Col 0. Try Row 1: Col 0 (conflict), Col 1 (conflict), Col 2 (safe). Try Row 2: all columns conflict โ†’ BACKTRACK. Row 1: Col 3 (safe). Row 2: Col 1 (safe). Row 3: all conflict โ†’ BACKTRACK further. Eventually place Q1 at (0,1), Q2 at (1,3), Q3 at (2,0), Q4 at (2,2) โ€” solution found.
  5. Two solutions exist for N=4: {(0,1),(1,3),(2,0),(3,2)} and {(0,2),(1,0),(2,3),(3,1)}.
  6. isSafe function: Check column (any queen in same column above), upper-left diagonal, upper-right diagonal.
  7. Time Complexity: O(N!) in the worst case. For N=8, only 92 solutions exist out of 8โธ โ‰ˆ 16.7M possible placements.
  8. Code: Provide the complete C++ implementation with isSafe() and solveNQueens() functions.

Q2. Compare memoization (top-down) and tabulation (bottom-up) approaches for solving the Fibonacci problem. Include code for both and analyze time/space complexity.

Answer Outline:

  1. Naive Fibonacci Problem: O(2โฟ) time due to overlapping subproblems. fib(5) computes fib(3) twice, fib(2) three times.
  2. Memoization (Top-Down): Start from fib(n), recursively break down, cache results. Code: use a dictionary/map. Time: O(n). Space: O(n) for cache + O(n) for stack = O(n).
  3. Tabulation (Bottom-Up): Start from fib(0), build up iteratively. Code: use an array. Time: O(n). Space: O(n) for array, but can be optimised to O(1) by keeping only last 2 values.
  4. Key Differences: Memoization is recursive (risk of stack overflow for large n), solves only needed subproblems. Tabulation is iterative (no stack risk), solves all subproblems from bottom.
  5. When to Use: Memoization when not all subproblems are needed (sparse problems). Tabulation when all subproblems are needed and space optimisation is critical.
  6. Provide both C++ and Python code for each approach.
  7. Performance comparison table: Time, space, stack risk, ease of implementation.
  8. Bridge to DP: Memoization is the entry point to Dynamic Programming. Mastering this makes DP (Unit 4) much easier.

Q3. Explain how memory is allocated during recursion. Draw the stack frame diagram for factorial(5) and discuss stack overflow prevention strategies.

Answer Outline:

  1. Stack Memory: Programs use a stack segment for function calls. Each call creates a "stack frame" containing local variables, parameters, return address, and saved registers.
  2. Stack Frame Diagram for factorial(5): Draw 6 frames (main + factorial(5) through factorial(0)). Show each frame's n value and return value. Show the stack growing upward (or downward depending on convention).
  3. Frame Size: Each frame is typically 32โ€“64 bytes. For factorial(5): 6 frames ร— ~50 bytes = ~300 bytes. For factorial(100000): 100001 frames ร— ~50 bytes = ~5 MB โ†’ exceeds default stack size.
  4. Stack Overflow: Occurs when recursion depth exceeds stack memory. Default stack: ~1 MB (Windows/Linux). Maximum safe depth: ~10,000โ€“30,000 calls.
  5. Prevention Strategies: (a) Always ensure base case is reachable. (b) Use tail recursion with TCO. (c) Convert to iteration for deep recursion. (d) Increase stack size via compiler flags. (e) Use memoization to reduce redundant deep calls.
  6. Python-specific: sys.setrecursionlimit() only raises the Python limit, not the OS stack. For truly deep recursion, use threading.stack_size() or convert to iteration.
  7. Real-world impact: Google Maps' DFS on billion-node graphs uses iterative DFS with explicit stack (a std::stack on the heap) to avoid stack overflow.
Section I

Syllabus Lab

๐Ÿ“‹ Lab Status

No specific lab is prescribed from the syllabus for this unit.

However, the 3-tier labs in Section D provide comprehensive hands-on practice:

  • Tier 1 (Guided): Factorial & Fibonacci with call tracing โ€” builds foundational understanding
  • Tier 2 (Semi-Guided): N-Queens solver โ€” applies backtracking concepts
  • Tier 3 (Open Challenge): Sudoku solver โ€” competition-level implementation

Complete all three tiers to build a strong recursion portfolio.

Section J

Industry Spotlight โ€” A Day in the Life

๐Ÿ‘จโ€๐Ÿ’ป Rohan Desai, 25 โ€” SDE-1 at Amazon India, Hyderabad

Background: B.Tech in Computer Science from a tier-2 college in Pune (PICT). No coding before college. Started competitive programming in 2nd year on Codeforces. Reached Expert rating (1600+) in 6 months. Practiced 500+ problems on LeetCode focusing on recursion, backtracking, and dynamic programming. Cleared Amazon's SDE-1 interview in 4th year โ€” 2 out of 3 coding rounds had recursion/backtracking problems.

A Typical Day:

9:30 AM โ€” Morning standup with the Amazon Logistics team. Review sprint tasks โ€” currently optimising the package routing algorithm.

10:00 AM โ€” Deep work: implementing a recursive graph traversal algorithm (DFS) for finding optimal delivery routes across 50 fulfilment centres. Uses backtracking to handle constraints (weight limits, time windows).

12:30 PM โ€” Code review with senior engineer. Discussion about converting a recursive solution to iterative for production (stack overflow risk with 1M+ nodes).

1:30 PM โ€” Lunch at the campus cafeteria. Discuss LeetCode weekly contest problems with teammates.

2:30 PM โ€” Write unit tests for the routing algorithm. Edge cases: circular routes, unreachable nodes, maximum depth limits.

4:00 PM โ€” 1-on-1 with manager. Career growth discussion โ€” targeting SDE-2 promotion by demonstrating system design skills.

5:30 PM โ€” Personal development: solving 2 LeetCode mediums to stay sharp. Today's theme: backtracking problems.

DetailInfo
Tools Used DailyC++, Java, Python, Git, AWS (Lambda, DynamoDB), LeetCode
Entry Salary (SDE-1)โ‚น12โ€“18 LPA + RSUs + signing bonus
Mid-Level (SDE-2, 3โ€“5 yrs)โ‚น25โ€“45 LPA
Senior (SDE-3, 7+ yrs)โ‚น50โ€“80 LPA
Key Interview TopicsRecursion, Backtracking, DP, Graphs, System Design
Companies HiringAmazon, Google, Microsoft, Flipkart, Swiggy, PhonePe, Atlassian, Uber, Goldman Sachs, DE Shaw
Rohan's advice: "I solved 200+ recursion and backtracking problems before my Amazon interview. In the actual interview, I got 'Word Search II' (a backtracking + trie problem) and 'Generate Parentheses'. Both were patterns I had practised. Recursion is the single most tested topic in FAANG interviews. Master it, and half the battle is won."
Section K

Earn With It โ€” Competitive Coding Tutoring

๐Ÿ’ฐ Your Earning Path After This Chapter

Portfolio Piece: GitHub repository with solved recursion problems (15+ problems), N-Queens solver, and Sudoku solver โ€” all with clean code, comments, and README documentation.

Earning Opportunities:

โ€ข 1-on-1 competitive coding tutoring for college students โ€” โ‚น300โ€“โ‚น800/hour

โ€ข Group coaching for placement preparation (recursion + backtracking module) โ€” โ‚น500โ€“โ‚น1500/session

โ€ข Creating LeetCode solution explanations on YouTube/blog โ€” ad revenue + sponsorships

โ€ข Freelance algorithm implementation for startups โ€” โ‚น5,000โ€“โ‚น20,000/project

PlatformBest ForTypical Rate
SuperProf1-on-1 tutoring for coding/DSAโ‚น400โ€“โ‚น1,200/hour
VedantuOnline group classes for Indian studentsโ‚น300โ€“โ‚น800/hour
FiverrAlgorithm implementation gigs$20โ€“$100/gig (โ‚น1,600โ€“โ‚น8,000)
UpworkCompetitive programming mentoring$15โ€“$50/hour
InternshalaCoding bootcamp teaching assistantโ‚น5,000โ€“โ‚น15,000/month
YouTube/BlogAlgorithm tutorials & explanationsโ‚น2,000โ€“โ‚น20,000/month (ad revenue)

โฑ๏ธ Time to First Earning: 3โ€“4 weeks (complete labs, build GitHub portfolio, register on SuperProf/Fiverr)

The fastest path to โ‚น10K/month: Tutor 3โ€“4 college juniors in DSA/competitive coding at โ‚น500/hour, 2 sessions/week each. That's 6โ€“8 hours/week = โ‚น12,000โ€“โ‚น16,000/month. Your edge? You've solved the problems and can explain them step-by-step. Start by helping classmates for free, then go paid.
Section L

Chapter Summary

๐Ÿ“‹ Key Takeaways โ€” Recursion & Advanced Techniques

  • Recursion = a function calling itself. Every recursive function needs a base case (stop) and recursive case (progress).
  • The call stack stores a frame for each recursive call. Depth n uses O(n) space.
  • Base case is critical โ€” without it, infinite recursion causes Stack Overflow.
  • Fibonacci illustrates the danger of naive recursion: O(2โฟ) time due to overlapping subproblems.
  • Divide and Conquer splits problems into independent halves โ€” merge sort, binary search, fast exponentiation.
  • Direct recursion (Aโ†’A) vs Indirect recursion (Aโ†’Bโ†’A).
  • Tail recursion = recursive call is the last operation. Can be optimised to O(1) space by compilers (TCO). Non-tail recursion requires O(n) stack.
  • Memory: Each stack frame stores local variables, parameters, and return address. ~32โ€“64 bytes per frame. Default stack ~1 MB.
  • Recursion vs Iteration: Recursion is elegant for trees/graphs; iteration is faster for simple linear problems.
  • Backtracking: Build solutions incrementally, abandon invalid paths early. Key problems: N-Queens, Sudoku, subset sum, permutations.
  • Memoization: Cache results of subproblems. Transforms O(2โฟ) Fibonacci to O(n). Bridges to Dynamic Programming (Unit 4).
  • Classic problems: Happy numbers, sum strings, champagne tower โ€” all leverage recursive thinking.
Section M

Earning Checkpoint โ€” Are You Ready to Monetise?

Topic/SkillTool/LanguagePortfolio OutputEarning Ready?
Recursion Basics (Factorial, Fibonacci)C++, PythonTraced programs with call stack diagramsโœ… Yes โ€” can tutor juniors
Backtracking (N-Queens, Permutations)C++N-Queens solver on GitHubโœ… Yes โ€” impressive for interviews & portfolio
MemoizationC++, PythonMemoized Fibonacci + complexity analysisโœ… Yes โ€” key interview topic
Sudoku SolverC++Complete Sudoku solver on GitHubโœ… Yes โ€” portfolio standout project
Competitive Coding PracticeLeetCode, Codeforces15+ solved problems with explanationsโœ… Yes โ€” can start tutoring at โ‚น500/hr
Problem-Solving PatternsConceptualโ€”โœ… Yes โ€” can create tutorial content
Minimum Viable Earning Setup after this chapter: A GitHub portfolio with N-Queens solver + Sudoku solver + 15 recursive problems + a SuperProf/Fiverr profile offering "DSA Tutoring & Competitive Coding Coaching" = you can earn โ‚น8,000โ€“โ‚น30,000/month from tutoring while still in college.

โœ… Unit 3 complete. Ready for Unit 4: Dynamic Programming!

[QR: Link to EduArtha video tutorial โ€” Recursion & Advanced Techniques]