skip to content

Why does Kotlin offer the `inline` keyword for higher-order functions, and what runtime cost does inlining remove?

level: juniorimportance: must knowfreq 70%

answer

  1. Lambda = FunctionN object + invoke() call
  2. inline copies body into call site
  3. Removes allocation + dispatch
  4. Enables non-local return + reified
  5. Best for small lambda-taking helpers

basics

~20 s

Normally passing a lambda creates an extra object and an extra method call. Marking the function inline copies the lambda's code straight into the caller, so no object is created and no extra call happens.

solid answer

~40 s

A higher-order function takes a lambda as a parameter. Without `inline`, the Kotlin compiler turns that lambda into a `Function` object (e.g. `Function1`) allocated on the heap, and invoking it costs a virtual `invoke()` call. For small helpers like `forEach`, `let`, or `repeat` called in hot loops, those allocations and megamorphic calls add up. Marking the function `inline` makes the compiler substitute the function body — and the bodies of its lambda parameters — directly at each call site, so the lambda object and the `invoke()` dispatch vanish. This also enables features that need the body present at compile time: non-local returns from the lambda and `reified` type parameters. The tradeoff is that the inlined bytecode is duplicated at every call site, so `inline` pays off for small functions taking lambdas, not large ones.

code

kotlin · 8 lines
kotlin
inline fun <T> measured(tag: String, block: () -> T): T {
    val start = System.nanoTime()
    val result = block()           // no Function object, no invoke() dispatch
    println("$tag took ${System.nanoTime() - start}ns")
    return result
}

val value = measured("load") { expensiveLoad() }  // body pasted at call site

go deeper

for a junior

Knows inline copies the lambda body in and avoids creating a lambda object.

for a middle

Explains the FunctionN allocation and invoke() dispatch that inlining removes, and names stdlib examples.

for a senior

Connects inlining to enabling non-local returns and reified, and notes the bytecode-bloat tradeoff.

for a principal

Reasons about when JIT would devirtualize anyway, and frames inline as a source-level guarantee with codegen/API consequences.

## What a higher-order function compiles to A *higher-order function* is one that takes a function (lambda) as a parameter or returns one. In Kotlin a lambda is a value, and by default the compiler represents it as an instance of a `FunctionN` interface (`Function0`, `Function1`, …). So this: ```kotlin fun runTwice(action: () -> Unit) { action(); action() } runTwice { println("hi") } ``` normally allocates a `Function0<Unit>` object for the lambda and calls its `invoke()` method twice. Two costs appear: - **Allocation**: a heap object per lambda (unless the compiler can cache a stateless one). - **Call overhead**: each `action()` is a virtual `invoke()` dispatch the JIT may not devirtualize. ## What `inline` does Marking the function `inline` tells the compiler to copy the function body — *and* the body of each lambda argument — into the call site: ```kotlin inline fun runTwice(action: () -> Unit) { action(); action() } // call site after inlining is effectively: println("hi"); println("hi") ``` No `Function0` is created, and there is no `invoke()` indirection. The standard library uses this for tiny building blocks: `forEach`, `map` on sequences, `let`, `run`, `also`, `apply`, `repeat`, `synchronized`, `use`. ## Why it matters - Hot loops calling `list.forEach { ... }` would otherwise allocate a lambda per call and dispatch through `invoke`. Inlining removes both. - Inlining is also what makes **non-local return** (`return` out of the enclosing function from inside the lambda) and **`reified`** type parameters possible, because the lambda/type is physically present at the call site. ## The tradeoff to remember The body is duplicated at every call site, so bytecode grows. `inline` is a win for *small* functions taking lambdas; for large bodies the bloat outweighs the saved allocation. The compiler even warns ("expected performance impact is insignificant") when you inline a function that has no functional parameters.

  • Does inlining always eliminate the lambda allocation?
    For lambdas passed to an `inline` function, yes — they're inlined and never allocated. But if a lambda parameter is marked `noinline`, or the lambda is stored/captured, an object may still be created.
  • Why does the compiler warn when you mark a function with no lambda parameters `inline`?
    Because the main benefit (removing lambda allocation/dispatch) only exists for functional parameters; without them you just duplicate bytecode for negligible gain.

Like pasting a short recipe step directly into your notes instead of writing 'see page 12' and flipping back every time.

saying these in an interview costs you the question

  • Claiming inline makes code run faster in all cases
  • Saying lambdas are 'free' even without inline
  • Confusing inline with the JIT's own inlining
  • Not knowing inline removes the lambda OBJECT, not just a call
  • Thinking inline only affects readability

context