Recursion Interview Problems - Symlink Cycle StackOverflow
A self-referencing symlink caused StackOverflowError after 5000 recursive calls in a backup agent.
- Recursion solves problems by breaking them into smaller self-similar subproblems
- Base case is the termination condition — missing it causes infinite recursion and StackOverflowError
- Four core patterns: Linear, Binary, Tail, Divide-and-Conquer
- Memoisation turns exponential binary recursion into linear time
- Backtracking follows make choice → recurse → undo choice; skipping undo corrupts branches
- Production risk: deep recursion (depth > 10,000) crashes JVM; prefer iteration or Trampoline for large inputs
Imagine you're looking for your lost keys in a house with many rooms. You open room 1, and instead of searching it yourself, you hire a clone of yourself to search it while you move on — but that clone does the same thing, hiring another clone for the next room. Each clone only needs to know one rule: 'search this room, then report back.' That's recursion. A function solves a tiny piece of a problem, then calls a copy of itself to handle the rest — until there's nothing left to solve.
Recursion shows up in almost every senior engineering interview. It's not because interviewers love brain teasers — it's because recursion is the natural language of problems that have self-similar structure: file systems, tree traversals, divide-and-conquer algorithms, and dynamic programming all lean on it. Companies like Google, Meta, and Amazon use recursion problems specifically because they reveal whether a candidate can break a complex problem into a repeatable, trustworthy unit of work.
The real problem most developers have with recursion isn't the concept — it's pattern blindness. They see a new problem and freeze because it looks unique, when in reality it fits one of four classic shapes. Once you can spot the shape, the solution almost writes itself. The mental shift is from 'how do I solve this whole thing?' to 'what's the one small thing I need to do right now, and can I trust the rest to a recursive call?'
By the end of this article you'll be able to identify which recursion pattern a problem belongs to, write clean base cases that don't blow the stack, trace recursive calls mentally without getting lost, and confidently explain your reasoning to an interviewer — which is half the battle.
The Four Recursion Patterns Every Interviewer Tests
Every recursion problem in an interview maps to one of four patterns. Learning to spot the pattern is more valuable than memorising solutions.
Pattern 1 — Linear Recursion: One recursive call per invocation. Classic examples: factorial, sum of a list, reversing a string. The call stack grows linearly with input size.
Pattern 2 — Binary/Branching Recursion: Two recursive calls per invocation. Fibonacci, binary tree traversal, merge sort. The call tree fans out and this is where O(2^n) danger lives if you're not careful.
Pattern 3 — Tail Recursion: The recursive call is the very last operation. Many languages and compilers can optimise this into a loop, killing stack overhead. Java doesn't do this automatically — but interviewers love asking about it.
Pattern 4 — Divide and Conquer: Split the problem into independent halves, recurse on each, then combine results. Merge sort and quicksort live here.
Knowing which pattern you're in tells you immediately what your time complexity will be and whether you need memoisation. A binary recursion with overlapping subproblems screams 'add a cache'. A linear recursion on a million-element list screams 'convert to iteration'.
Solving the Classic Tree Problems Interviewers Love
Binary tree problems are the most common recursion questions in FAANG-style interviews. They feel hard until you realise every single one follows the same three-line mental model: handle the null base case, do something at this node, recurse left and right.
The secret is trusting your recursive calls. The biggest mistake candidates make is trying to mentally trace the entire recursion in their head. You don't need to. You only need to answer: 'If my recursive call correctly returns the answer for a subtree, what do I do with it at this node?'
Take finding the maximum depth of a binary tree. At each node, you trust that maxDepth(node.left) gives you the correct depth of the left subtree and maxDepth(node.right) gives you the right. Your only job at this node is: 1 + max(leftDepth, rightDepth). That's it. The whole algorithm is that one insight.
Memoisation — Turning Exponential Recursion into Linear
Naive binary recursion on problems with overlapping subproblems is a performance disaster. fibonacciNaive(40) makes over a billion function calls. The fix — memoisation — is one of the highest-value techniques you can demonstrate in an interview because it shows you understand both recursion and dynamic programming.
The insight is simple: if you've already computed the answer for a subproblem, cache it. On the next call with the same input, return the cached answer instantly instead of recomputing from scratch.
Backtracking — Recursion That Knows How to Undo Its Mistakes
Backtracking is recursion with a safety net. It explores a path, and if that path leads nowhere, it undoes the last step and tries another direction. Think of it like solving a maze by always drawing on the walls with chalk — if you hit a dead end, you erase back to the last junction and try a different turn.
Backtracking shows up in: generating all permutations, solving Sudoku, the N-Queens problem, and finding all subsets of a set. These are problems where you're building a solution piece-by-piece and some partial solutions turn out to be invalid.
new ArrayList<>(currentSubset) — not currentSubset itself. Passing the reference means every result in your list will point to the same object and mutate as you backtrack. This is the #1 silent bug in backtracking code.Recursion vs Iteration: When to Use Which in Production
Recursion is elegant for problems that mirror the data structure (trees, graphs). But production systems often ban recursion for anything that handles user input because of stack safety. Iteration with an explicit stack (or queue) gives you control over memory and avoids the JVM's fixed stack size.
- Recursion: cleaner code, but O(depth) stack space, risk of overflow for deep inputs.
- Iteration: more verbose, but O(1) extra space (if no manual stack), no overflow risk.
- Memoisation works naturally with recursion but can be replicated with tables in iteration (bottom-up DP).
- Backtracking is nearly always implemented recursively because tracking state with a manual stack is painful.
In interviews, you can start with recursion for clarity, then mention you'd convert to iteration if depth is a concern. That's senior-level thinking.
- Recursion uses the JVM's call stack — size fixed at startup (default ~1024 KB).
- Iteration lets you use the heap for the manual stack — heap can grow much larger.
- Recursion is the 'free' stack; iteration is 'explicit' stack.
- If depth > 10,000, recursion fails; iteration with heap stack handles millions.
Infinite Recursion via Symlink in File System Scanner
- Never trust that input structure is acyclic — always apply cycle detection in recursion that traverses user-provided data.
- Add a maximum recursion depth guard even for well-known structures. The JVM stack isn't infinite.
- Log recursion depth periodically in production loops to detect abnormal growth before it blows the stack.
Key takeaways
Common mistakes to avoid
3 patternsMissing or wrong base case
Mutating shared state without backtracking
Storing a reference instead of a snapshot in backtracking
results.add(currentList), you're storing a reference to a list that will keep changing. Always do results.add(new ArrayList<>(currentList)) to capture the state at that moment.Interview Questions on This Topic
Explain the concept of 'Stack Overflow' in the context of recursion. How does the JVM handle deep call stacks?
Frequently Asked Questions
That's Coding Patterns. Mark it forged?
4 min read · try the examples if you haven't