skip to content

inline Functions

inline copies the function body and its lambda arguments into each call site, so no FunctionN object is created. This is why let, run, and with cost nothing, and why marking your own hot higher-order function inline can matter.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

Explain how `inline` lets stdlib scope functions like `let`, `run`, and `with` be used freely without allocation overhead. What would change if they were NOT inline?

level: middleimportance: must knowfreq 70%

basics

~20 s

These helpers are tiny functions that take a lambda. Because they are marked inline, the compiler pastes their body and your lambda into the call site, so no extra object is made. If they were not inline, every call would create a lambda object.

open as a page

When should you mark your own function `inline`, and when is it a mistake? Give the rule of thumb the compiler itself nudges you toward.

level: middleimportance: should knowfreq 55%

basics

~20 s

Mark a small function inline when it takes a lambda and you want to avoid creating an object for that lambda. Don't inline big functions or ones without lambda parameters — it just bloats the code with no real benefit.

open as a page

Walk through what the compiler actually generates when an `inline` HOF is called. Address: the lambda object, the function-body copy, captured variables, and one thing inlining does NOT do.

level: seniorimportance: should knowfreq 45%

basics

~20 s

At each call, the compiler drops the inline function's body and your lambda's body straight into the caller. No object is made for the lambda, and variables it uses are just read in place. It does not, however, run anything concurrently or change results.

open as a page

A teammate claims 'lambdas in Kotlin are always cheap because the compiler reuses them.' How do you reason precisely about when a lambda allocates versus when `inline` makes it free? Mention the non-capturing-lambda nuance.

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

It's not automatic. A lambda passed to a normal function becomes an object. inline removes that object entirely. Separately, a lambda that captures nothing can sometimes be reused as a single shared instance, but that's different from inlining.

open as a page