Can all recursive functions be re-written as tail-recursions?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Yes, any recursive function can be mechanically transformed into a tail-recursive version using an accumulator parameter or continuation-passing style (CPS). However, not all recursive functions can be trivially rewritten — functions that make multiple recursive calls (like tree traversal or Fibonacci) require techniques like CPS or explicit stack simulation. The practical value of tail recursion depends on whether the language performs tail-call optimization (TCO): Scheme, Haskell, Kotlin, and Scala do; Python, Java, and most JavaScript engines do not.
Standard Recursion vs Tail Recursion
In tail recursion, the recursive call is the very last thing the function does — no computation happens after it returns. This allows the compiler to reuse the current stack frame, eliminating stack growth.
Technique 1: Accumulator Parameter
For linear recursion (one recursive call), add an accumulator that carries the intermediate result:
Technique 2: Continuation-Passing Style (CPS)
For tree recursion (multiple recursive calls), accumulators are not enough. CPS transforms the "what to do next" into an explicit function parameter:
CPS makes the function tail-recursive, but the continuations consume heap memory instead of stack. The total memory is the same — it is just moved from stack to heap.
Technique 3: Explicit Stack (Iterative Simulation)
Any recursion can be converted to iteration with an explicit stack:
Which Functions Are Trivially Tail-Recursive?
| Pattern | Trivially tail-recursive? | Technique needed |
| Linear recursion (one call) | Yes | Accumulator |
| Linear with post-processing | Yes | Accumulator |
| Tree/multi-branch recursion | No | CPS or explicit stack |
| Mutual recursion (A calls B, B calls A) | Possible | Trampoline or CPS |
| Divide and conquer (merge sort) | No | CPS or iterative |
Language Support for Tail-Call Optimization
| Language | TCO? | Notes |
| Scheme/Racket | Yes (required by spec) | All tail calls optimized |
| Haskell | Yes | Lazy evaluation complicates analysis |
| Scala | Yes (@tailrec) | Self-recursive calls only |
| Kotlin | Yes (tailrec) | Self-recursive calls only |
| Erlang/Elixir | Yes | Critical for process loops |
| JavaScript | Partial | Only Safari supports TCO (ES6 spec requires it) |
| Python | No | Guido explicitly rejected TCO |
| Java | No | JVM does not support tail calls |
| C/C++ | Compiler-dependent | GCC/Clang optimize in -O2 |
Trampoline: TCO Without Language Support
The trampoline replaces recursion with a loop that repeatedly calls returned functions, avoiding stack growth.
Common Pitfalls
- Assuming tail recursion prevents all stack overflow: Tail recursion only helps if the language implements TCO. In Python and Java, tail-recursive functions still overflow the stack because these languages do not optimize tail calls.
- CPS does not reduce memory usage: CPS moves memory consumption from the stack to the heap (closures). It prevents stack overflow but does not reduce total memory usage. For deeply recursive functions, iterative solutions with explicit stacks are more memory-efficient.
- Forgetting that
@tailrec(Scala) andtailrec(Kotlin) only work for self-recursion: These annotations optimize direct self-calls only. Mutual recursion (A calls B, B calls A) is not optimized. Use trampolines for mutual tail recursion. - Making a function "look" tail-recursive without actually being tail-recursive:
return 1 + f(n-1)is NOT tail-recursive because addition happens after the recursive call. The call must be the absolute last operation — no wrapping computation. - Premature optimization with tail recursion: In many languages, a simple iterative loop is clearer and equally performant. Tail recursion is most valuable in functional languages where loops do not exist. In Python or Java, prefer iteration for performance-critical code.
Summary
- Any recursive function can be rewritten as tail-recursive using accumulators (linear) or CPS (tree)
- Tail recursion only eliminates stack growth if the language performs tail-call optimization (TCO)
- Languages with guaranteed TCO: Scheme, Haskell, Scala (
@tailrec), Kotlin (tailrec), Erlang - Languages without TCO: Python, Java, most JavaScript engines
- Use trampolines to achieve TCO-like behavior in languages without native support
- For tree recursion, explicit stack iteration is often simpler and more efficient than CPS

