skip to content

What does the `inline` modifier do to a Kotlin function, and why is it most useful on functions that take lambda parameters?

level: juniorimportance: must knowfreq 80%

answer

  1. Copies body into call site
  2. No FunctionN allocation, no invoke() dispatch
  3. Powers let/run/with/apply/also
  4. Best with lambda params
  5. Cost = bigger bytecode

basics

~20 s

inline tells the compiler to copy the function's body straight into each place it is called, instead of making a real function call. This avoids creating an object for the lambda you pass in, so it runs faster.

solid answer

~40 s

Marking a function `inline` makes the compiler substitute the function body at every call site rather than emitting an actual JVM method invocation. The big win is for higher-order functions: normally a lambda passed as an argument is compiled to a `FunctionN` object (e.g. `Function1`) that gets allocated and invoked virtually. When the function is `inline`, both the function body and the lambda body are copied into the caller, so no `FunctionN` instance is created and there is no extra call indirection. That is exactly how the stdlib scope functions `let`, `run`, `with`, `apply`, and `also` and collection ops like `forEach`/`map` stay zero-overhead. The trade-off is larger bytecode at each call site, so `inline` pays off when there are lambda params, not on ordinary functions.

code

kotlin · 8 lines
kotlin
inline fun <T> T.alsoLog(tag: String, block: (T) -> Unit): T {
    println("$tag: $this")
    block(this)
    return this
}

// At the call site no Function1 object is created for { ... }
val n = 42.alsoLog("value") { println(it * 2) }

go deeper

for a junior

Can state that inline copies the body into the call site and avoids creating a lambda object.

for a middle

Names FunctionN allocation and invoke() dispatch as the concrete costs removed, and cites the scope functions as examples.

for a senior

Frames it as the enabler of zero-overhead HOFs and notes the bytecode-size trade-off and the no-functional-param warning.

for a principal

Reasons about when inlining is a net win across a codebase (hot small HOFs vs. code-size blowup) and how it interacts with JIT and other inline-only features.

## What `inline` means The `inline` keyword is a modifier on a function. It instructs the Kotlin compiler to perform **inlining**: instead of generating a normal method call, the compiler **copies the function's compiled body directly into every call site**. A **call site** is any place in your code where the function is invoked. A **higher-order function (HOF)** is a function that takes another function (a lambda) as a parameter or returns one. ## Why lambdas normally cost an allocation When you pass a lambda to a regular (non-inline) function, the Kotlin compiler turns that lambda into an **object** implementing a `FunctionN` interface — `Function0`, `Function1`, … depending on the number of arguments. Calling the lambda is a **virtual call** through `invoke()`. So a plain HOF involves: - allocating a `FunctionN` object (heap pressure, potential GC), - a virtual `invoke()` dispatch each time the lambda runs. ## What inlining removes With `inline`, the compiler copies **both** the function body **and** the lambda body into the caller. Result: - **No `FunctionN` object** is allocated for the lambda. - **No `invoke()` indirection** — the lambda's statements sit inline in the caller. ```kotlin inline fun measure(block: () -> Unit) { val start = System.nanoTime() block() println("took ${System.nanoTime() - start} ns") } fun work() { measure { println("hello") } } ``` After inlining, `work()` is compiled roughly as if you had written the timing code and `println("hello")` directly inside `work` — no lambda object, no extra method. ## Where you already rely on this The standard library **scope functions** `let`, `run`, `with`, `apply`, `also` are all `inline`, as are `repeat`, `forEach`, `map`, `filter`, `takeIf`, `synchronized`, and `runCatching`. That is why using them in hot loops carries essentially no overhead versus hand-written code. ## The cost Inlining duplicates bytecode at each call site, so a large `inline` function called in many places **increases code size**. The feature is intended for **small functions with lambda parameters**; the compiler even warns ("expected performance impact is insignificant") if you mark an `inline` function that has no functional parameters.

  • Does `inline` change the observable behavior of the function?
    No — it is purely a codegen optimization. Same results; only the generated bytecode and performance characteristics differ (plus it enables non-local returns and `reified`, which are separate features).
  • Is it worth marking a function with no lambda parameters `inline`?
    Usually not; the JIT already optimizes ordinary calls. The compiler warns it has insignificant benefit. You'd only do it to enable `reified` type parameters.

Like copy-pasting a snippet everywhere you'd call it, instead of phoning a shared helper each time.

saying these in an interview costs you the question

  • Saying inline makes the function run on a separate thread or is async
  • Claiming inline always makes code faster regardless of size
  • Confusing `inline` with `inline class` / `value class`
  • Thinking it inlines the call but still allocates the lambda object
  • Believing it changes the function's results, not just its codegen

context