Making recursion more efficient with tailrec
What is recursion?
Recursion is the programming technique of calling the function within the same function. A recursive function uses the result from the previous recursive call. The perfect example for recursion is fibonacci(n)
.
If we have to describe in the code:
fun fibonacci(n: Long) : Long {
return when (n) {
0L -> 0
1L -> 1
else ->
fibonacci(n - 1) + fibonacci(n - 2)
}
}
Tail Recursion
While it is easy to read the recursive function, it’s expensive. When calling a function, the information about that function and its arguments are stored in call stack
. When you call a recursive function, each recursive invocation adds a frame to the call stack. This can easily produce a StackOverflowError
which means it runs out of memory.
To prevent call stacks overflows, functional languages (including Kotlin) use a technique call tail recursion. The goal is to reduce the size of the call stack. The way to do is by using the keyword tailrec
. Under the right conditions, this will convert recursive calls into iteration, eliminating call-stack overhead. This is a complier optimisation , but it doesn’t work for all recursive calls.
To use tailrec
successfully, recursion must be the final operation, which means there can be no extra calculations on the result of the recursive call before it is returned.
Remember that fibonacci
implementation - it is terribly inefficient because the previously-calculated results are not reused. Thus, the number of operations grows exponentially.
With tail recursion, the calculations become dramatically more efficient.
fun fibonacci(n: Int): Long {
tailrec fun fibonacci(
n: Int,
current: Long,
next: Long
): Long {
if (n == 0) return current
return fibonacci(
n - 1,
next,
current + next
)
}
return fibonacci(n, 0L, 1L)
}
We use the local function to conceal the fibonacci
function so that the user cannot put other values in those defaults, which produce incorrect results. The only function that the user can see is fibonacci(n)
.
Summary
While it is easy to read, it’s expensive. When calling a function, the information about that function and its arguments are stored in call stack
. When you call a recursive function, each recursive invocation adds a frame to the call stack. This can easily produce a StackOverflowError
which means it runs out of memory.
Use tailrec
technique wherever applicable to make the function more efficient.