DSA

Part 15 — Recursion

A practical, interview-focused guide to recursion in Python: recursive decomposition, base cases, call-stack reasoning, backtracking, divide-and-conquer, memoization, invariants, complexity, and Staff-level interview patterns.

Deepak Mishra18 min read


Recursion is the natural next step after the core data structures and algorithms covered in the earlier parts.

The provided source places Chapters 15–18 in the advanced-algorithm section and identifies recursion, dynamic programming, greedy algorithms, and graphs as the major themes.

Important source note: The available source material identifies Chapters 15–18 as the advanced algorithm sequence, including recursion, but it does not expose the complete numbered Chapter 15 problem list. Therefore, this part focuses on the recursion concepts, patterns, invariants, and interview reasoning supported by the source rather than inventing an exact problem-by-problem reproduction.


1. What Recursion Is Really Teaching

Don’t think of recursion as merely:

“A function that calls itself.”

The important idea is:

Large problem

smaller instance

solve smaller instance

combine result

The Staff-level question is:

What smaller problem is structurally identical to the original problem?

That is the heart of recursion.


2. The Three Parts of Every Recursive Algorithm

A recursive solution normally needs:

1. Base case
2. Recursive case
3. Progress toward the base case

Example:

def factorial(n):
    if n <= 1:
        return 1

    return n * factorial(n - 1)

Breakdown:

Base case:
n <= 1

Recursive case:
n * factorial(n - 1)

Progress:
n → n - 1

If the recursive call does not make measurable progress toward termination, the algorithm can recurse forever.


3. The Recursive Mental Model

For:

factorial(4)

think:

factorial(4)

4 * factorial(3)

       3 * factorial(2)

             2 * factorial(1)

                         1

Then return:

1

2

6

24

The important distinction is:

CALL phase

recursive descent

RETURN phase

combine results

Many recursive algorithms perform their important work during the return phase.


4. Recursion and the Call Stack

Every recursive call creates a stack frame.

For:

factorial(4)

the stack conceptually becomes:

factorial(4)
factorial(3)
factorial(2)
factorial(1)

Then frames are removed in reverse order.

Therefore:

recursive depth = h

usually means:

auxiliary stack space = O(h)

The source explicitly warns that the function-call stack counts toward space complexity. For recursion depth h, space is O(h); balanced tree recursion is typically O(log n), while skewed recursion can reach O(n).


5. Base Case Is a Correctness Condition

A weak explanation is:

“I need a base case so recursion stops.”

A stronger explanation is:

“The base case is the smallest instance for which the answer is directly known. The recursive case reduces the problem to that smaller instance.”

Example:

def sum_list(nums, i):
    if i == len(nums):
        return 0

    return nums[i] + sum_list(nums, i + 1)

The base case represents:

sum of an empty suffix = 0

That is a mathematical statement, not merely a stopping condition.


6. Recursion Template

A useful interview template is:

def solve(state):
    if is_base_case(state):
        return base_answer(state)

    smaller_state = reduce(state)
    smaller_answer = solve(smaller_state)

    return combine(state, smaller_answer)

Before coding, explain:

What is the state?
What is the smaller state?
What is the base case?
How do I combine the result?

The source’s broader interview guidance emphasizes making reasoning visible rather than merely typing code.


7. Recursion on Trees

Binary trees are naturally recursive.

A tree consists of:

root
+
left subtree
+
right subtree

Each subtree is itself a tree.

Therefore:

def inorder(root):
    if root is None:
        return

    inorder(root.left)
    print(root.data)
    inorder(root.right)

The recursive structure directly mirrors the data structure.

The source identifies recursion as a core algorithmic technique and emphasizes recognizing when a problem asks you to aggregate information from children and return it to the parent.


8. The “Solve Children, Then Parent” Pattern

A very important tree-recursion pattern is:

             parent
             /    \\
        solve L   solve R
             \\    /
              combine

              parent

Example: compute tree height.

def height(root):
    if root is None:
        return 0

    left_height = height(root.left)
    right_height = height(root.right)

    return 1 + max(left_height, right_height)

The recursive calls solve smaller problems.

The parent combines them:

height(root)
=
1 + max(
    height(left),
    height(right)
)

This is one of the most reusable recursion patterns in interviews.


9. Divide and Conquer

Recursion frequently appears with divide and conquer.

The pattern is:

Problem

Split
 /   \\
A     B
↓     ↓
solve solve
 \\   /
 combine

The source explicitly identifies divide-and-conquer as a fundamental algorithmic pattern and gives merge sort and binary search as examples.


10. Merge Sort as Recursive Decomposition

Conceptually:

[8 3 5 1]

[8 3] [5 1]
  ↓     ↓
[8][3] [5][1]
  ↓     ↓
[3 8] [1 5]

[1 3 5 8]

The recursive structure is:

sort(left)
sort(right)
merge(left, right)

Python:

def merge_sort(nums):
    if len(nums) <= 1:
        return nums

    mid = len(nums) // 2

    left = merge_sort(nums[:mid])
    right = merge_sort(nums[mid:])

    return merge(left, right)

The recurrence is approximately:

T(n) = 2T(n/2) + O(n)

which gives:

O(n log n)

time.


11. Recursion Tree

For divide-and-conquer, visualize the calls:

                  n
               /     \\
             n/2     n/2
            /  \\     /  \\
          n/4 n/4  n/4 n/4
             ...

Each level processes approximately:

O(n)

work.

The number of levels is:

O(log n)

Therefore:

O(n) × O(log n)
=
O(n log n)

This way of deriving complexity is more valuable than memorizing the answer.


12. Binary Search Is Recursive Divide and Conquer

For sorted input:

check middle

discard half

recurse on remaining half

Recursive version:

def binary_search(nums, target, left, right):
    if left > right:
        return -1

    mid = (left + right) // 2

    if nums[mid] == target:
        return mid

    if target < nums[mid]:
        return binary_search(nums, target, left, mid - 1)

    return binary_search(nums, target, mid + 1, right)

Complexity:

Time: O(log n)
Space: O(log n)

because of recursive stack depth.

An iterative implementation can reduce auxiliary space to:

O(1)

This is an important interview trade-off.


13. Recursion vs Iteration

Whenever you see recursion, ask:

“Do I actually need recursion?”

Approach Advantage Cost
Recursion Natural structure Call-stack overhead
Iteration Explicit control Sometimes more complex
Recursion + memoization Elegant overlapping-state solution Cache + stack memory

For a tree, recursion may communicate the structure clearly.

For very deep input, iteration may be safer because Python has a recursion-depth limit.

Staff-level reasoning means recognizing this trade-off.


14. Backtracking

Backtracking is recursion plus controlled exploration.

The pattern is:

choose

recurse

undo

The source’s earlier recursion example describes exactly this pattern for generating combinations: choose, recurse, and overwrite/restore the current state.

General template:

def backtrack(state):
    if is_complete(state):
        result.append(copy(state))
        return

    for choice in choices(state):
        make(choice, state)
        backtrack(state)
        undo(choice, state)

This pattern appears in:

subsets
permutations
combinations
constraint problems
path enumeration

15. Why Backtracking Works

Suppose choices are:

A
B
C

and each choice leads to more choices.

The recursion tree becomes:

             ""
          /   |   \\
         A    B    C
       / | \\ /|\\ /|\\
      ...       ...

Each root-to-leaf path represents one candidate solution.

The algorithm explores:

choose
→ extend
→ evaluate
→ undo
→ try next choice

16. Pruning

Backtracking becomes practical when we can reject impossible partial solutions early.

Pattern:

generate candidate

check constraint

valid?
 /     \\
no     yes
↓       ↓
prune   recurse

The Staff-level question is:

What information lets me prove that an entire branch cannot produce a valid solution?

The source repeatedly emphasizes finding structure that eliminates unnecessary work.


17. Example: Generate Subsets

For:

[1, 2, 3]

each element gives two choices:

include
exclude

Python:

def subsets(nums):
    result = []
    current = []

    def backtrack(i):
        if i == len(nums):
            result.append(current.copy())
            return

        backtrack(i + 1)

        current.append(nums[i])
        backtrack(i + 1)
        current.pop()

    backtrack(0)
    return result

There are:

2^n

subsets.

If every subset is returned, output construction also contributes to total cost. The source explicitly distinguishes recursive-call complexity from the cost of constructing returned outputs.


18. Recursion + State

A powerful way to understand recursive problems is to define the state precisely.

For subsets:

state = current index + selected elements

For tree recursion:

state = current node

For binary search:

state = left, right

For backtracking:

state = current partial solution + next choice

The better you define the state, the easier the recursive solution becomes.


19. Recursion + Invariant

A recursive invariant says:

What is guaranteed to be true whenever this function is called?

Example:

def binary_search(nums, target, left, right):

Invariant:

If target exists,
it must be within nums[left:right+1].

Every recursive call preserves that invariant.

Eventually:

left > right

and the search space is empty.

This gives you a correctness proof.


20. Correctness Proof Pattern

For recursive algorithms, use induction-like reasoning:

Base case

Show the answer is correct for the smallest problem.

Recursive assumption

Assume the recursive call correctly solves the smaller problem.

Combine step

Show that combining the smaller answer with the current state produces the correct answer.

Therefore:

base case correct
+
smaller problem correct
+
combine step correct
=
whole algorithm correct

21. Recursion Complexity

Always separate:

number of calls

from:

work performed per call

For example:

T(n) = 2T(n/2) + O(n)

gives:

O(n log n)

But:

T(n) = 2T(n-1) + O(1)

can become exponential.

Don’t judge complexity simply because:

“There is recursion.”

The recurrence determines the complexity.


22. Recursion + Memoization

Sometimes recursion recomputes the same subproblem.

Example:

                 f(5)
               /     \\
             f(4)    f(3)
             / \\      / \\
           ...       ...

The same states can appear repeatedly.

Solution:

recursion
    +
cache

memoization

Python:

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n

    return fib(n - 1) + fib(n - 2)

Without caching, naive Fibonacci recursion has exponential growth.

With caching:

Time: O(n)
Space: O(n)

The source describes dynamic programming using this same idea: solve smaller states and cache them so repeated subproblems are not recomputed.


23. Recursion → Dynamic Programming

This is the conceptual bridge to Part 16.

Recursive problem

Notice repeated states

Cache results

Memoization

Dynamic programming

When recursion feels exponentially expensive, ask:

Am I solving the same state repeatedly?

If yes, caching may remove the repeated work.


24. Recursion → Backtracking

Another branch is:

Recursion

Multiple choices

Explore each choice

Backtracking

Ordinary recursion often reduces a problem into smaller instances, while backtracking explores a decision tree of possible solutions.


25. Recursion → Divide and Conquer

Another branch is:

Recursion

Split problem

Solve independent pieces

Combine

Divide and conquer

Examples include:

merge sort
binary search
recursive tree algorithms

The source explicitly identifies divide and conquer as a fundamental algorithmic pattern.


26. Recursion Pattern Map

                    Recursion
                        |
          +-------------+-------------+
          |             |             |
          v             v             v
     One smaller    Multiple       Split problem
       problem       choices
          |             |             |
          v             v             v
      Recursive      Backtracking   Divide &
       descent                      conquer
          |             |             |
          v             v             v
       Trees        subsets etc.   merge sort
       structures   permutations   binary search
                        |
                        v
                     Pruning

And another path:

Recursion

Repeated states

Memoization

Dynamic Programming

27. How to Recognize a Recursive Problem

Ask:

1. Can I express the problem using a smaller version of itself?

2. Is the input naturally hierarchical?

3. Does the problem split into independent pieces?

4. Are there multiple choices that form a decision tree?

5. Can I define a clean base case?

6. Does every recursive call reduce the problem?

If the answer is mostly yes, recursion is a strong candidate.


28. When NOT to Use Recursion

Don’t use recursion merely because it is elegant.

Prefer iteration when:

input can be extremely deep
stack depth may become large
recursive state is simple
Python recursion depth is a concern

For example:

while current:
    ...

may be preferable to recursive traversal of a potentially very deep chain.

Staff-level reasoning means recognizing this trade-off.


29. Python-Specific Considerations

Python recursive calls create stack frames and Python does not generally optimize tail calls.

Therefore:

recursive depth = large

can lead to:

RecursionError

For production code, don’t increase the recursion limit blindly without understanding the workload.

Instead ask whether an iterative algorithm is more appropriate.


30. Recursive State vs Explicit State

A useful mental transformation is:

Recursive function parameters
+
call stack

can often be converted into:

explicit stack object

This is how recursive DFS becomes iterative DFS.

Therefore recursion is not magic.

It is often:

An implicit stack plus a state transition.


31. Backtracking and State Restoration

The most common backtracking bug is forgetting to undo state.

Bad:

current.append(x)
backtrack(...)
# forgot to remove x

Correct:

current.append(x)
backtrack(...)
current.pop()

The invariant is:

Before exploring a sibling branch,
state must be restored to its previous value.

This is the essence of:

choose
→ recurse
→ undo

32. Pruning as Complexity Optimization

Suppose a brute-force search explores:

2^n

possibilities.

If constraints allow us to prune large portions of the search tree, actual runtime can be dramatically smaller for many inputs.

The Staff-level question is:

What information lets me prove that an entire branch cannot produce a valid solution?

That is the same fundamental optimization principle emphasized throughout the source:

Find structure

Eliminate unnecessary work

33. Recursion Interview Template

When given a recursion problem, say:

“I want to define the smallest meaningful instance first. Then I’ll identify how the current problem reduces to that smaller instance. The recursive call solves the smaller problem, and I’ll combine that result with the current state.”

Then state:

State:
...

Base case:
...

Recursive transition:
...

Invariant:
...

Complexity:
...

This makes your reasoning visible.


34. Staff-Level Interview Example

Interviewer:

“Generate all possible combinations.”

Weak response:

“I’ll use recursion.”

Strong response:

“The output is a set of paths through a decision tree. At each position I have a finite set of choices. I’ll maintain the current partial solution, recurse after making a choice, and restore the state before exploring the next choice. If a partial solution violates a constraint, I’ll prune that branch.”

That demonstrates:

model
+
state
+
invariant
+
search
+
pruning

The source describes this choose/recurse/overwrite pattern for recursive enumeration.


35. Recursion Complexity Checklist

After writing recursive code, ask:

How many recursive calls?

How many children per call?

How much work per call?

What is the maximum depth?

Is there repeated state?

Is output itself exponential?

Does recursion use O(h) stack?

Then derive:

Time = ?
Space = recursion stack + auxiliary state + output

36. Common Recursion Mistakes

Mistake 1 — No progress

return solve(state)

The state never changes.

Mistake 2 — Incorrect base case

The recursion stops too early or too late.

Mistake 3 — Wrong state

The function does not carry enough information to solve the subproblem.

Mistake 4 — Forgetting restoration

Common in backtracking.

Mistake 5 — Ignoring repeated states

May turn an otherwise manageable recursive solution into exponential time.

Mistake 6 — Ignoring call-stack space

Recursive calls consume memory. The source explicitly calls this out as a common senior-level mistake.


37. Recursion Decision Tree

Can problem be expressed
as a smaller version?
        |
       YES

     Recursion
        |
        +----------------+
        |                |
        v                v
One main path       Multiple choices
        |                |
        v                v
Tree / divide       Backtracking
and conquer             |
                         v
                      Pruning

Then ask:

Repeated subproblems?
        |
       YES

   Memoization

       DP

38. Recursion → Part 16

The transition is:

Part 15 — Recursion

Define state

Find smaller problem

Notice repeated states

Cache states

Part 16 — Dynamic Programming

The source’s DP framework is:

Problem

smaller state

state[i]

dependency on previous states

cache states

39. Recursion → Part 17

Another transition is:

Recursive search

Multiple possible choices

Can one choice always be proven safe?

Greedy reasoning

The source defines greedy algorithms as making locally optimal choices without undoing them and emphasizes the Staff-level question:

Why is the greedy choice globally safe?

This gives a useful contrast:

Backtracking
→ explore alternatives

Greedy
→ prove one alternative is safe

40. The Deep Connection: Recursion Is About Structure

The most important lesson is not:

function calls itself

It is:

Problem structure

smaller equivalent problem

state transition

base case

composition

This is why recursion appears in:

trees
divide & conquer
backtracking
dynamic programming
graph traversal
parsing
search

41. Staff-Level Mental Model

When you see a recursive problem, don’t immediately code.

Think:

What is the state?

What is the smallest valid state?

What is the base case?

How does the state shrink?

What does the recursive call guarantee?

How do I combine its result?

What invariant is preserved?

How many states/calls exist?

What is the maximum recursion depth?

Are states repeated?

Can I prune?

Would iteration be safer?

This is the same broader Staff-level discipline used throughout the interview guide: clarify, model, solve, prove, code, test, and optimize.


42. Final Recursion Cheat Sheet

Pattern Core Idea Typical Complexity
Linear recursion Reduce by one O(n)
Binary search Halve search space O(log n)
Tree recursion Solve children O(n)
Divide & conquer Split + solve + combine often O(n log n)
Subsets Include / exclude O(2^n) states
Backtracking Choose / recurse / undo problem-dependent
Memoized recursion Cache repeated states often polynomial
Recursive DFS Implicit stack O(V+E) for graphs

Always distinguish:

time
+
recursion stack
+
auxiliary state
+
output size

43. The One Formula to Remember

For a recursive algorithm:

Current problem
      =
local work
+
recursive subproblems

So derive:

T(n)
=
recursive work
+
non-recursive work

Then solve the recurrence.

That is far more powerful than memorizing complexity numbers.


44. Final Takeaway

The most important thing about recursion is not knowing how to write:

return solve(smaller_problem)

It is knowing why that smaller problem is equivalent to the original problem.

Remember:

Recursion

State

Base case

Smaller problem

Invariant

Combine

And the three major directions:

Recursion
   ├── Divide & Conquer
   │      ├── Merge Sort
   │      └── Binary Search

   ├── Backtracking
   │      ├── Choose
   │      ├── Recurse
   │      ├── Undo
   │      └── Prune

   └── Repeated States

       Memoization

          DP

For Staff/Principal interviews, the strongest answer is not:

“I know recursion.”

It is:

“I can define the recursive state, establish the invariant, prove the base and transition cases, derive the recurrence, reason about stack space, and explain when recursion should be replaced by iteration, pruning, or memoization.”


Part 15 in the Series

Part 13 — Sorting

Part 14 — Binary Search Trees

Part 15 — Recursion

Part 16 — Dynamic Programming

Part 17 — Greedy Algorithms

Part 18 — Graphs

The progression is:

Sorting

Create order

BST

Maintain order

Recursion

Decompose problems

Dynamic Programming

Reuse overlapping subproblems

Greedy

Prove local choices are globally safe

Graphs

Model relationships and connectivity

This sequence turns the earlier data-structure knowledge into a more powerful algorithmic reasoning toolkit.