ProgrammingKotlin developer

How does the 'tailrec' keyword work in Kotlin, what is it used for, what are the requirements for tail recursion, and what are the typical mistakes when using it?

Pass interviews with Hintsage AI assistant

Answer.

Background

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.

The Problem

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).

The Solution

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:

  • The function must be recursive, and the last operation must be a call to itself.
  • 'tailrec' can only be applied to functions that are not open or abstract.
  • There are limitations, such as not being usable within lambdas or when the immediate tail call is shadowed by other operations.

Tricky Questions.

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.

Common Mistakes and Anti-patterns

  • Misusing 'tailrec' in a non-tail position, leading to a compilation error.
  • Trying to use 'tailrec' for lambdas or functions with unusual return structures.
  • Applying 'tailrec' where recursion is trivial and can be more simply replaced by a regular loop.

Real-life Example

Negative Case

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:

  • Desire to make the code optimal. Cons:
  • Compilation fails, logic is broken, recursive calls may not work.

Positive Case

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:

  • Reliability.
  • Optimized memory usage. Cons:
  • A more complex style of writing (recursion with an accumulator).