When an interview question talks about breaking a problem into similar pieces, you’re probably looking at recursion. The idea is simple: a function calls itself with a smaller input until it reaches a condition that can be answered directly – the base case. The surrounding calls then combine those answers to produce the final result.
Why Recursion Exists
- Natural fit for hierarchical data – trees, nested lists, and file systems map directly to recursive definitions.
- Divide‑and‑conquer – sorting (quick‑sort, merge‑sort) and searching (binary search) split the input in half each step.
- Combinatorial explosion – generating permutations, subsets, or paths often follows a "choose‑or‑skip" pattern that recurses.
Signals That a Problem Wants Recursion
| Phrase in the prompt | Typical pattern |
|---|---|
| "All possible ..." | Backtracking / enumeration |
| "Traverse a tree" | Tree recursion |
| "Divide the array" | Divide‑and‑conquer |
| "Find the nth ..." | Direct mathematical recurrence |
| "Reduce the problem" | General recursion |
If you see any of these, pause and sketch the sub‑problem relationship before reaching for a loop.
Worked Example: "Count the number of ways to climb n stairs with 1‑ or 2‑step moves"
The problem asks for the total number of distinct sequences of steps that sum to n. Each climb either starts with a 1‑step or a 2‑step, leaving a smaller sub‑problem.
def climb_ways(n: int) -> int:
"""Return the number of ways to reach the top of a staircase of length n.
"""
# Base cases
if n < 0:
return 0 # overshoot – no valid way
if n == 0:
return 1 # exact hit – one empty sequence
# Recursive step: choose 1‑step or 2‑step first
return climb_ways(n - 1) + climb_ways(n - 2)
Complexity
- Time – Without memoization the recurrence repeats many sub‑problems, yielding exponential time O(2^n). Adding a cache (or converting to DP) reduces it to linear O(n).
- Space – Call stack depth equals n in the worst case, so O(n) auxiliary space.
Common Pitfalls
- Missing base case – leads to infinite recursion and a stack overflow.
- Wrong base case values – e.g., returning 0 for
n == 0makes every answer zero. - Repeated work – naive recursion can recompute the same sub‑problem many times; memoization or bottom‑up DP is the fix.
- Mutating shared state – if you use a list to accumulate results, remember to copy or backtrack correctly.
Five Representative Practice Problems
1. "Generate all subsets of a set"
Hint: For each element, recursively generate subsets that include it and subsets that don’t. Use a list to accumulate the current subset and backtrack after the recursive call.
2. "Validate a binary search tree"
Hint: Recursively ensure every node’s value lies within a range passed from its ancestors. Update the range when moving left or right.
3. "Find the lowest common ancestor of two nodes in a binary tree"
Hint: Return the node if it matches either target, otherwise recurse left and right. The first node that receives non‑null results from both sides is the LCA.
4. "Word break: can a string be segmented into dictionary words"
Hint: Try every possible prefix; if the prefix is in the dictionary, recurse on the suffix. Memoize results for each start index to avoid exponential blow‑up.
5. "Maximum path sum in a binary tree"
Hint: For each node, compute the maximum sum of a path that starts at that node and goes downwards. The global maximum may pass through the node, combining left and right contributions.
When Not to Use Recursion
- Strict space limits – deep recursion can exceed the call‑stack size; an iterative solution with an explicit stack may be safer.
- Performance‑critical loops – if you can express the solution with a simple loop and constant extra space, that’s often preferred.
- Languages without tail‑call optimization – Python, JavaScript, and most mainstream languages don’t optimise tail calls, so deep recursion can be costly.
How to Practice This
- Pick one of the five problems each day and write a recursive solution from scratch. Then, refactor it to an iterative version and compare.
- Run a timer on inputs that cause exponential behavior. Add memoization and observe the speed‑up; this reinforces why caching matters.
- Explain the recursion out loud while a friend listens or using Call Assistant to record yourself. Speaking forces you to articulate the base case, the recurrence, and the termination condition clearly.
FAQ
Q: How do I know if a problem is better solved with recursion or iteration? A: Look for a natural self‑similar structure. If you can define the answer in terms of the same problem on a smaller input, recursion is a good first attempt. After you have a working version, consider whether the call stack depth will be large; if so, an iterative approach may be safer.
Q: What is the safest way to add memoization in Python? A: Use
functools.lru_cacheas a decorator, or maintain a dict keyed by the function arguments. It avoids manual bookkeeping and keeps the code readable.Q: Why does a missing base case cause a stack overflow rather than a logical error? A: Without a terminating condition, the function keeps calling itself forever, each call consuming stack memory until the language runtime aborts the program.
Q: Can recursion handle problems with cycles, like graphs that aren’t trees? A: Yes, but you must track visited nodes to prevent infinite loops. Adding a
visitedset to the recursive parameters is a common pattern.
Frequently asked questions
How do I know if a problem is better solved with recursion or iteration?
Look for a natural self‑similar structure. If you can define the answer in terms of the same problem on a smaller input, recursion is a good first attempt. After you have a working version, consider whether the call stack depth will be large; if so, an iterative approach may be safer.
What is the safest way to add memoization in Python?
Use `functools.lru_cache` as a decorator, or maintain a dict keyed by the function arguments. It avoids manual bookkeeping and keeps the code readable.
Why does a missing base case cause a stack overflow rather than a logical error?
Without a terminating condition, the function keeps calling itself forever, each call consuming stack memory until the language runtime aborts the program.
Can recursion handle problems with cycles, like graphs that aren’t trees?
Yes, but you must track visited nodes to prevent infinite loops. Adding a `visited` set to the recursive parameters is a common pattern.
#coding pattern#recursion#interview prep#python#algorithm