Recursion is a useful technique commonly found in functional languages and in Kotlin. However, direct recursion often leads to stack overflow (StackOverflowError). To combat this, many languages have introduced tail call optimization. In Kotlin, it is explicitly implemented through the 'tailrec' keyword.
Regular recursion creates new stack frames with each call. When the depth of recursion is large, this leads to a StackOverflowError. Automatic optimization of tail recursion is not always possible, and the JVM does not support it as some other language compilers do (Scala, Erlang).
Kotlin allows marking a function with the 'tailrec' keyword. If the function is indeed a tail recursion (i.e., the resulting call to itself is the final operation), the compiler replaces it with a loop, thereby eliminating stack overflow.
Code example:
tailrec fun factorial(n: Int, acc: Int = 1): Int = if (n == 0) acc else factorial(n - 1, acc * n) println(factorial(5)) // 120
Key features:
What happens if 'tailrec' is used but the call is not in the tail position?
The compiler will produce an error and will not apply the optimization.
tailrec fun sum(n: Int, acc: Int = 0): Int { println(n) // after this, optimization is not possible return if (n == 0) acc else sum(n - 1, acc + n) } // Compilation error: call not in tail position
Can 'tailrec' be used in open functions or abstract methods?
No, the keyword only works on final functions.
open class Base { // Error: // tailrec open fun test() {} }
Is there a performance difference between regular recursion and 'tailrec'?
Yes, 'tailrec' converts the function into a loop, reducing stack usage and eliminating StackOverflowError.
A developer marks all recursive functions as 'tailrec' without considering whether optimization could apply. Calls are not always in tail position — as a result, projects do not compile, and recursion does not work efficiently.
Pros:
A developer analyzes the logic, applies tail recursion in functions such as tree search or factorial. 'tailrec' is used where it is genuinely applicable. An effectively compiled loop arises without stack overflow.
Pros: