skip to content

Inline Machinery

Inlining is what lets Kotlin offer higher-order functions without an allocation per call, and it is also what enables non-local returns and reified type parameters. Every serious Kotlin performance question passes through here.

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

explore

questions

30

What does the crossinline modifier do on a lambda parameter of an inline function?

level: juniorimportance: must knowfreq 55%

answer

  1. Inlined but no non-local return
  2. Needed when lambda runs in another context (Runnable/nested lambda)
  3. Only return@label allowed inside
  4. Middle ground: inline < crossinline < noinline
  5. Compiler error without it when capturing the lambda

basics

~20 s

crossinline marks a lambda of an inline function so it cannot use a non-local return. It is still inlined, but you are not allowed to write 'return' to exit the calling function from inside it.

solid answer

~40 s

In an inline function, lambda parameters are inlined into the call site, which lets them use non-local returns: a plain 'return' inside the lambda returns from the enclosing function. crossinline keeps the lambda inlined but forbids that non-local return. You apply it when the lambda is not called directly inside the inline function body but is instead passed into another execution context, for example stored in a Runnable, an object, or a nested lambda, where allowing a return-from-caller would be impossible or unsafe. The lambda body is still inlined (no separate function object created for the simple path), so you keep inlining benefits, but only local 'return@label' returns are permitted. It is the middle ground between a normal inline lambda (non-local return allowed) and noinline (not inlined at all).

code

kotlin · 11 lines
kotlin
inline fun runLater(crossinline body: () -> Unit) {
    val r = Runnable { body() }   // body invoked in another execution context
    r.run()
}

fun demo() {
    runLater {
        // return        // ERROR: non-local return forbidden
        return@runLater   // OK: local return only
    }
}

go deeper

for a junior

Can state that crossinline keeps the lambda inlined but blocks non-local returns and is needed when the lambda is captured elsewhere.

for a middle

Explains the call-site-copying mechanism behind non-local returns and why capturing into a Runnable breaks it.

for a senior

Contrasts crossinline vs noinline vs plain inline precisely and picks the right one for a given indirect-invocation scenario.

for a principal

Reasons about codegen and API ergonomics: when to expose crossinline vs noinline in a public inline DSL to balance inlining wins against caller flexibility.

## The problem crossinline solves When a function is marked `inline`, the compiler copies the function body and its lambda arguments directly into the call site. Because the lambda body ends up physically inside the caller, a bare `return` inside that lambda can return from the **enclosing function** — this is called a **non-local return**. ```kotlin inline fun forEachItem(list: List<Int>, action: (Int) -> Unit) { for (item in list) action(item) } fun find(list: List<Int>): Int { forEachItem(list) { item -> if (item > 10) return item // non-local return: returns from find() } return -1 } ``` This works because `action` is invoked **directly** inside `forEachItem`'s own body. But sometimes you don't call the lambda directly — you pass it somewhere else (another lambda, a `Runnable`, an anonymous object). In that case a non-local return is impossible: by the time the lambda runs, the caller's stack frame may be gone. ## What crossinline does `crossinline` tells the compiler: "keep inlining this lambda, but **forbid non-local returns** from it." The lambda is still inlined (so you avoid allocating a function object on the simple path), but only **local returns** (`return@label`) are allowed. ```kotlin inline fun runLater(crossinline body: () -> Unit) { val r = Runnable { body() } // body used in another execution context r.run() } ``` Without `crossinline`, the compiler would reject `Runnable { body() }` because the lambda is captured into another context where a non-local return cannot be honored. With `crossinline` the code compiles, and any caller's lambda may only do local returns: ```kotlin runLater { // return // COMPILE ERROR: 'return' not allowed here return@runLater // OK: local return, exits only this lambda } ``` ## How it differs from the alternatives - **Plain inline lambda** — inlined, non-local return allowed (the default for inline-function lambdas). - **crossinline lambda** — inlined, non-local return **forbidden**; needed when the lambda runs in a different execution context. - **noinline lambda** — **not** inlined at all; becomes a real function object you can store/pass freely; non-local return also impossible. ## Key keywords and APIs involved - `inline`, `crossinline`, `noinline` modifiers - non-local `return` vs local `return@label` - function types like `() -> Unit`, used as `Runnable`, captured in nested lambdas or anonymous objects ## Mental rule Use `crossinline` when you want the lambda inlined **and** you must use it indirectly (inside a `Runnable`, a callback object, a nested lambda). If you instead need to store it in a field or pass it onward as a value, use `noinline`.

  • Is a crossinline lambda still inlined?
    Yes. crossinline only removes the non-local return capability; the body is still inlined into the call site, so you keep inlining benefits.
  • What return is still legal inside a crossinline lambda?
    A labeled local return such as return@functionName, which exits only the lambda itself.

Like a guest you bring into a room but who isn't allowed to flip the building's main breaker — they can only switch off the lamp in their own corner.

saying these in an interview costs you the question

  • Saying crossinline turns off inlining (it does not — that's noinline)
  • Claiming crossinline allows non-local returns
  • Confusing crossinline with noinline
  • Believing crossinline is about performance rather than control flow
  • Thinking it forbids all returns, including local return@label

context

open as a page

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%

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.

open as a page

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

level: juniorimportance: must knowfreq 70%

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.

open as a page

What does the `noinline` modifier do when applied to a lambda parameter of an `inline` function?

level: juniorimportance: must knowfreq 55%

basics

~20 s

It tells Kotlin NOT to inline that one lambda. Instead the lambda stays a normal object you can store in a variable or pass to another function, while the other lambdas in the inline function are still inlined.

open as a page

What is a non-local return in Kotlin, and why does `return` inside `forEach { }` exit the enclosing function?

level: juniorimportance: must knowfreq 60%

basics

~20 s

A plain return inside a lambda can jump out of the whole surrounding function, not just the lambda. This works in forEach because forEach is an inline function, so the lambda's body is copied directly into your code.

open as a page

What is a `reified` type parameter in Kotlin, and why does it require the function to be `inline`?

level: juniorimportance: must knowfreq 70%

basics

~20 s

reified lets a generic function know its actual type at runtime. Normally that type is erased and forgotten. It only works on inline functions because the compiler copies the code in and fills in the real type.

open as a page

In what situation does the Kotlin compiler require you to mark an inline function's lambda parameter as crossinline?

level: middleimportance: must knowfreq 50%

basics

~20 s

When you don't call the lambda directly in the function body but instead use it inside another context — like a Runnable, an anonymous object, or a nested lambda — the compiler forces crossinline because a non-local return wouldn't be possible there.

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

Inlining isn't free. Describe concrete situations where marking a function `inline` hurts more than it helps.

level: middleimportance: must knowfreq 60%

basics

~20 s

Inlining copies the whole function body into every place it's called. If the body is big or it's called from many places, your bytecode balloons. And if the function has no lambda parameter, you gain almost nothing.

open as a page

Give a concrete scenario where you MUST use `noinline`, and explain why the compiler forces it.

level: middleimportance: must knowfreq 48%

basics

~20 s

You need noinline when you want to keep the lambda as a value — for example to return it, store it in a list, or hand it to another function that isn't inline. The compiler refuses to compile those uses on an inlined lambda because there's no object to use.

open as a page

Why is a bare `return` allowed inside a lambda passed to an `inline` function but a compile error inside a non-inline one?

level: middleimportance: must knowfreq 55%

basics

~20 s

An inline function's lambda is pasted directly into your code, so a return there is really inside your function and can return from it. A non-inline lambda is a separate object that might run later, so there is nothing valid to return from — hence the error.

open as a page

Compare crossinline and noinline: how do they differ in inlining behavior and what each lets you do with the lambda?

level: middleimportance: should knowfreq 45%

basics

~20 s

crossinline keeps the lambda inlined but bans non-local returns. noinline does not inline the lambda at all, turning it into a real object you can store or pass around. Both block non-local returns; only noinline lets you keep the lambda as a value.

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

Explain the core tradeoff inlining makes between heap allocation/call overhead and bytecode size, and how you'd decide for a given function.

level: middleimportance: should knowfreq 40%

basics

~20 s

Inlining trades one thing for another: it removes the lambda object and extra call, but copies the function's code into every call site, making the program bigger. Inline when the body is small and called a lot; skip it when the body is big.

open as a page

Compare `noinline` and `crossinline`: what does each control, and when do you pick one over the other?

level: middleimportance: should knowfreq 50%

basics

~20 s

Both apply to a lambda parameter of an inline function. noinline un-inlines the lambda so it becomes a real object you can store or pass on. crossinline keeps it inlined but bans return that would exit the caller, which is needed when the lambda runs from another context.

open as a page

Contrast `return`, `return@forEach`, and `return@label` inside a lambda. When do you use each?

level: middleimportance: should knowfreq 45%

basics

~20 s

Plain return exits the whole surrounding function. return@forEach exits only the current lambda call, like skipping to the next item. A custom return@myLabel does the same but uses a label you defined, which is handy when lambdas are nested.

open as a page

How does `filterIsInstance<T>()` work, and why couldn't you write its exact signature without `reified`?

level: middleimportance: should knowfreq 45%

basics

~10 s

filterIsInstance<T>() keeps only the elements that are of type T. It needs reified because, without the real type at runtime, the function couldn't perform the is T check on each element.

open as a page

Inside a `reified` function, what is the difference between `T::class` and `typeOf<T>()`, and when do you need each?

level: middleimportance: should knowfreq 50%

basics

~10 s

T::class gives you the class object only. typeOf<T>() gives you the full type — including nullability and inner generic types like List<String>. Use typeOf when nested generics or nullability matter.

open as a page

Inside a crossinline lambda, which kinds of return statements are legal and which are rejected, and why?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A labeled local return like return@functionName is allowed — it exits just the lambda. A bare non-local return that would exit the calling function is rejected, because the lambda may run in a context where the caller is no longer on the stack.

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

Why does the Kotlin compiler forbid a `public inline` function from referencing `private` members of its class, and how does `@PublishedApi` relate to this?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Because an inline function's body gets copied into the code that calls it. If that body touched something private, the caller's compiled code would reference a member it isn't allowed to see. @PublishedApi lets you mark an internal member as safe to expose to such inlined code.

open as a page

What are the codegen and performance implications of marking a lambda `noinline`, and how does it interact with variable capture?

level: seniorimportance: should knowfreq 32%

basics

~20 s

A noinline lambda becomes a real object, so it costs an allocation and a virtual call, just like a lambda passed to any normal function. If it captures no variables Kotlin can reuse a single shared instance; if it captures, a new object is created each call.

open as a page

You inline a function that runs its lambda inside a nested `Runnable` object. Why does the compiler force `crossinline`, and how does that change non-local returns?

level: seniorimportance: should knowfreq 35%

basics

~20 s

If the inlined lambda is called from inside another object (like a Runnable), it could run later, so a non-local return would be unsafe. The compiler makes you mark the lambda crossinline, which keeps it inlined but bans bare return from it.

open as a page

Why can't you forward a non-reified type parameter into a `reified` slot, and what patterns work around it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A normal type parameter is erased at runtime, so you have no real type to hand to a reified function. To bridge it, pass the type as a value — a KClass or Class — instead of relying on reified.

open as a page

You maintain a published Kotlin library. What policy would you adopt for `inline` functions on your public API, and why?

level: principalimportance: should knowfreq 30%

basics

~20 s

Use public inline sparingly. Because the body is copied into every user's compiled code, changing it later can break them without warning. Reserve inline for tiny helpers that truly need it, and keep the body stable.

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

You're designing a higher-order API. How do you decide between making a function `inline` with a `noinline` parameter versus just writing a plain function?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Make it inline only if at least one lambda benefits from inlining (hot calls, non-local returns, or reified types). Use noinline for the one lambda you must keep as an object. If every lambda needs to be an object, just write a normal function.

open as a page

What are the practical hazards of non-local returns in scope functions like `let`/`run`, and how do they interact with refactoring a non-inline HOF?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Because let/run/forEach are inline, a return inside them quietly exits the whole function — easy to misread. And if you later change a custom inline helper to non-inline, every non-local return callers wrote suddenly stops compiling.

open as a page

You are designing a public inline higher-order API where the lambda is invoked from a background callback. How do you decide between crossinline and noinline, and what are the downstream consequences for callers and codegen?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

If you only need to call the lambda (even from a callback), use crossinline so it stays inlined with no allocation. If you must store or pass the lambda onward as a value, use noinline. Both stop callers from using non-local returns, which changes how they write control flow.

open as a page

When designing a library API, how do you decide between a `reified` inline overload and a `KClass`/type-token parameter, and what are the long-term tradeoffs?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Use reified for clean call sites when the type is known at compile time. Use a KClass/Class parameter when the type is dynamic or you want a reflective entry point. Big libraries usually offer both, with reified delegating to the token version.

open as a page