skip to content

What does the `tailrec` modifier do in Kotlin, and why would you use it?

level: juniorimportance: should knowfreq 55%

answer

  1. tailrec = recursion rewritten to a loop
  2. Call must be the LAST operation (tail position)
  3. Avoids StackOverflowError, constant stack space
  4. Not last? compiler warns, falls back to plain recursion
  5. Accumulator parameter makes the call tail

basics

~10 s

tailrec tells the compiler to turn a function that calls itself at the very end into a plain loop. This avoids the program crashing with a stack overflow on deep recursion.

solid answer

~40 s

`tailrec` is a function modifier that asks the Kotlin compiler to perform tail-call optimization (TCO): it rewrites a self-recursive function whose recursive call is the last operation into an iterative loop. Normal recursion pushes a new stack frame per call, so deep recursion throws `StackOverflowError`; the `tailrec` version reuses a single frame, running in constant stack space. The recursive call must be in tail position — its result is returned directly with nothing done to it afterward. If the compiler cannot apply the optimization (e.g. the call isn't the last operation), it emits a warning and the function stays ordinary recursion. You typically use it for iterative algorithms expressed recursively: computing a fixed point, walking a linked structure, or accumulator-style loops.

code

kotlin · 6 lines
kotlin
tailrec fun sum(n: Long, acc: Long = 0): Long =
    if (n == 0L) acc else sum(n - 1, acc + n)

fun main() {
    println(sum(1_000_000)) // no StackOverflowError
}

go deeper

for a junior

Knows tailrec turns end-recursion into a loop to avoid StackOverflowError.

for a middle

Can explain tail position and rewrite a function with an accumulator to satisfy it.

for a senior

Discusses the compiler warning-only fallback and the direct-self-recursion limit.

for a principal

Frames it as a readability vs. safety trade-off and knows the bytecode is a plain loop with no runtime cost.

## What `tailrec` is `tailrec` is a Kotlin **function modifier** (a keyword you put before `fun`) that requests **tail-call optimization (TCO)**. The compiler rewrites a self-recursive function into an ordinary `while` loop. ## Why it matters: the stack Every function call pushes a **stack frame** (space for parameters and locals) onto the call stack. The JVM stack is finite, so a function that recurses thousands of times eventually throws **`StackOverflowError`**. A **tail call** is a recursive call that is the **last thing** the function does — its result is returned directly with no further work. When that's the case, the current frame is no longer needed, so the compiler can **reuse the same frame** instead of allocating a new one. The result runs in **constant stack space**, exactly like a hand-written loop. ## The rule: the call must be in tail position The recursive call must be the final operation. These are NOT tail calls and will defeat the optimization: - `return n * factorial(n - 1)` — the multiply happens *after* the recursive call. - `return factorial(n - 1) + 1` — addition after the call. - A recursive call inside a `try`/`catch` block — the runtime must keep the frame to handle exceptions. The fix is usually an **accumulator parameter** that carries the running result, so the call really is last. ```kotlin tailrec fun factorial(n: Long, acc: Long = 1): Long = if (n <= 1) acc else factorial(n - 1, acc * n) // last operation -> tail call ``` ## What the compiler does if it can't optimize If you mark a function `tailrec` but the call is not in tail position, the compiler emits a **warning** ("A function is marked as tail-recursive but no tail calls are found" or that the call is not a tail call) and compiles it as **ordinary recursion** — it does not error out, but you lose the protection. ## Limits - Only **direct self-recursion** is supported — function A calling itself, not mutual recursion (A calls B calls A). - It cannot be combined with `open` (an overridable open function can't be safely optimized). - Each recursive call site must be in tail position. ## Mental model Think of `tailrec` as syntactic sugar: you write clear recursion, the compiler hands you a loop.

  • What happens if you mark a function `tailrec` but the recursive call is not in tail position?
    The compiler emits a warning and compiles it as ordinary recursion, so it can still throw StackOverflowError.
  • Does `tailrec` work for mutual recursion (A calls B calls A)?
    No — only direct self-recursion is optimized; mutual recursion is not supported.

Like reusing the same desk for each step of a long calculation instead of fetching a brand-new desk every time and stacking them to the ceiling.

saying these in an interview costs you the question

  • Claiming `tailrec` makes any recursion safe regardless of where the call is
  • Saying it throws a compile error (it only warns) when the call isn't tail
  • Confusing it with `inline` or with general performance optimization
  • Believing it works across two mutually-recursive functions

context