Tail Call Optimization
We'll cover the following
What is tail call optimization?
Consider the code you write to be a procedure and the generated bytecode that will eventually run to be a process. The factorialIterative()
function is an iterative procedure and is compiled into and will run as an iterative process—no surprise there. Likewise, factorialRec()
is a recursive procedure and is compiled into, and run as, a recursive process, exactly what we’d expect there as well. However, the real gain, as explained in
That’s intriguing—compiling recursion into iteration—that’s exactly what the tailrec
annotation will instruct the Kotlin compiler to do. Let’s rewrite the factorialRec()
function to make use of that technique.
Using tailrec
As a first step, we’ll annotate the function with the tailrec
keyword.
tailrec fun factorialRec(n: Int): BigInteger =
if (n <= 0) 1.toBigInteger() else n.toBigInteger() * factorialRec(n - 1)
Good try, but that doesn’t work—we need to take a few more steps.
120
recursivetail.kts:4:1: warning: a function is marked as tail-recursive
but no tail calls are found
tailrec fun factorialRec(n: Int): BigInteger =
^
recursivetail.kts:5:56: warning: recursive call is not a tail call
if (n <= 0) 1.toBigInteger() else n.toBigInteger() * factorialRec(n - 1)
^
Kotlin is too polite here and gives a warning, but remember to treat warnings as errors as discussed in Sensible Warnings. If we try to run the function for large inputs, this version’s fate will be the same as the older version of factorialRec()
, a runtime error. Kotlin will optimize recursion to iteration only when the call is in tail position. Let’s discuss that further.
Looking at the code n.toBigInteger() * factorialRec(n - 1)
, we may be tempted to think that factorialRec()
is executed last, but it’s the multiplication operation that kicks in before the function call returns. This operation waits for a call to factorialRec()
to finish, thus increasing the stack size on each recursive call. A tail call is where the recursive call is truly the last operation in the function. Let’s rewrite the function factorialRec()
and rename it as factorial()
along the way.
Get hands-on with 1200+ tech skills courses.