Tail recursion — why does Java not optimize it? — Cracked Java
// Data Structures & Algorithms · Recursion & Backtracking
SeniorTheoryTrickGoogle

Tail recursion — why does Java not optimize it?

A tail-recursive call is one where the recursive call is the last operation in the method — nothing is left to do with its result except return it. Because the caller's frame is no longer needed, a compiler could reuse it instead of pushing a new one, turning the recursion into a loop with O(1) stack. This is tail-call optimization (TCO).

// Tail-recursive: nothing happens after the recursive call.
long fact(int n, long acc) {
    if (n <= 1) return acc;
    return fact(n - 1, acc * n);   // last action — a tail call
}

// NOT tail-recursive: the multiply happens AFTER the call returns.
long factNaive(int n) {
    if (n <= 1) return 1;
    return n * factNaive(n - 1);   // still has work pending
}

Why Java does not do it

The JVM deliberately keeps every frame. The reasons are baked into the platform:

  • Stack traces — Java guarantees a full, accurate stack trace for every exception. Eliding frames would lose those entries; debuggability was chosen over the optimization.
  • Security modelAccessController and stack-walking APIs inspect the actual call stack for caller-sensitive permission checks. Reusing frames would corrupt that view.
  • javac doesn't emit it, the JIT doesn't add it — the bytecode keeps the invoke/return pair, and the JIT does not rewrite tail calls into jumps. (The MethodHandle.invokeExact machinery has limited internal tail-call support, but it is not exposed to ordinary recursion.)

The consequence: even perfectly tail-recursive Java code costs O(depth) stack and will StackOverflowError on large input. The @tailrec you may know from Scala has no Java equivalent.

What to do instead

Rewrite the tail recursion as the loop it already is:

long fact(int n) {
    long acc = 1;
    for (int i = 2; i <= n; i++) acc *= i;   // the accumulator becomes a local
    return acc;
}

For non-tail (branching) recursion that goes too deep, simulate the call stack with an explicit Deque.

Mark your status