liveBook · Manning

Tail recursion

As explained in 100 Java Mistakes and How to Avoid Them

Tail recursion is recursion in which the recursive call is the final operation performed by the method.

Tail recursion makes the recursive call the method’s final operation. This form can be optimized into iteration, but Java does not guarantee that optimization, so sufficiently deep calls may still cause StackOverflowError. section 5.4.1 section 5.5

Accumulate before the recursive call

A summation function can carry the partial result in an accumulator. When the limit reaches zero, it returns the accumulator; otherwise, it decreases the limit and makes the recursive call with the updated result. Kotlin’s tailrec modifier can turn this pattern into a loop in JVM bytecode, allowing large inputs such as 1,000,000. section 5.4.1

tailrec fun sum(limit: Long, accumulator: Long = 0): Long {
  if (limit == 0L) return accumulator
  return sum(limit - 1, accumulator + limit)
}

Shows the accumulator-based Kotlin pattern whose recursive call is the final operation. section 5.4.1

Use these distinctions when applying or reviewing tail recursion:

  • Treat the recursive call as tail-recursive only when no operation remains afterward; accumulating the result before the call preserves that property. section 5.4.1
  • Do not assume Java will optimize tail recursion: some virtual machines may do so during JIT compilation, but the behavior is not guaranteed. section 5.4.1
  • Rewrite tail recursion as a loop when stack depth matters; IntelliJ IDEA offers Replace Tail Recursion With Iteration. section 5.4.1 section 5.5
  • Use heap-based structures such as ArrayDeque for non-tail recursive traversals, and track visited elements when cycles are possible. section 5.5
/**
 * @param s string to search in
 * @param start start offset
 * @param c symbol to search
 * @return position of symbol c inside string s
 * starting from start offset; -1 if not found
 */
static int indexOf(String s, int start, char c) {
  if (start >= s.length()) return -1;
  if (s.charAt(start) == c) return start;
  return indexOf(s, start + 1, c);
}

Provides a tail-recursive string index search whose call is the final operation. section 5.4.1

static int indexOf(String s, int start, char c) {
  while (true) {
    if (start >= s.length()) return -1;
    if (s.charAt(start) == c) return start;
    start = start + 1;
  }
}

Shows the equivalent iterative index search, avoiding possible stack overflow. section 5.4.1

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Explains accumulators, compiler optimization, stack overflow, and mechanical loop rewriting, but does not guarantee Java optimization.
Examples
Demonstrates accumulator-based summation and recursive versus iterative string index search.
What you can do after
Apply tail recursion with accumulators, recognize optimization limits, and rewrite suitable methods as loops.
How it connects
Connects tail recursion with recursion, iteration, loops, stack overflow, JVM bytecode, and heap-based data structures.

Also explained in

Compare how 4 books explain tail recursion →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.