skip to content

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

level: middleimportance: must knowfreq 50%

answer

  1. Indirect use triggers the requirement
  2. Runnable / anonymous object / nested lambda capture
  3. Compiler: 'add crossinline modifier'
  4. crossinline keeps inline; noinline drops it
  5. Direct call in body needs nothing

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.

solid answer

~40 s

By default an inline function's lambda may be invoked directly in the body, which is what enables non-local returns. The compiler requires crossinline (or noinline) whenever you pass the lambda into a context where it might be called after the inline function's frame is no longer on the stack: storing it in a Runnable, calling it from inside a nested lambda or local function, or invoking it from an anonymous object/SAM instance. In all those cases a non-local return from the caller cannot be guaranteed, so Kotlin refuses to keep the default 'non-local return allowed' inline lambda. crossinline keeps inlining while disabling the non-local return; noinline drops inlining entirely. If you only need to call the lambda directly in the body, neither modifier is needed.

code

kotlin · 7 lines
kotlin
inline fun schedule(crossinline task: () -> Unit) {
    // task is invoked inside another execution context
    executor.execute(Runnable { task() })
}

// Without crossinline this fails to compile:
// "Can't inline 'task' here: it may contain non-local returns"

go deeper

for a junior

Recognizes that passing the lambda into a Runnable forces an extra modifier.

for a middle

Names the concrete triggers (Runnable, nested lambda, anonymous object) and explains they break non-local returns.

for a senior

Chooses correctly between crossinline and noinline based on whether the lambda is merely invoked or must be stored.

for a principal

Anticipates these constraints when designing inline APIs, shaping signatures so callers keep non-local returns where it matters most.

## The default and why it has limits For an `inline` function, each lambda parameter is, by default, a fully inlined lambda that supports **non-local returns** — a bare `return` inside it returns from the function that called the inline function. This only works because the compiler can copy the lambda body into the call site and the call happens **directly** in the inline function's body. ## When the compiler demands crossinline (or noinline) The moment the lambda is used **indirectly**, the non-local-return guarantee breaks, and the compiler stops you. Common triggers: - **Captured in another object** (e.g. a `Runnable`, `Comparator`, or other SAM/anonymous object): ```kotlin inline fun schedule(crossinline task: () -> Unit) { executor.execute(Runnable { task() }) // indirect: needs crossinline } ``` - **Called from inside a nested lambda or local function** rather than the top-level body: ```kotlin inline fun retry(times: Int, crossinline block: () -> Unit) { val wrapped = { block() } // nested lambda captures block repeat(times) { wrapped() } } ``` - **Invoked from an anonymous object** implementing an interface. In each case the compiler error is roughly: *"Can't inline 'block' here: it may contain non-local returns. Add 'crossinline' modifier to the parameter declaration"*. ## What you must add and the trade-off You have two legal fixes: - `crossinline` — stays inlined, forbids non-local returns. Use when you still want inlining and the lambda doesn't need to be stored as a value. - `noinline` — not inlined, becomes a real `Function` object you can store in a field or return. Use when you need to keep the lambda around as a value. ```kotlin // stored for later -> must be noinline inline fun register(noinline callback: () -> Unit) { handlers += callback } ``` ## Decision guide - Call lambda directly in body, want non-local return -> leave it plain. - Call it indirectly but immediately (Runnable, nested lambda) -> `crossinline`. - Need to **store/return** the lambda as a value -> `noinline`. ## Keywords involved `inline`, `crossinline`, `noinline`, non-local `return`, SAM conversion (`Runnable`), nested lambdas, local functions.

  • If I need to store the lambda in a list field, should I use crossinline?
    No — use noinline. crossinline lambdas are still inlined and cannot be held as a value; storing requires a real function object, which noinline provides.
  • Does calling the lambda inside repeat { } directly require crossinline?
    No. repeat is itself inline, so the lambda is still effectively called in an inline context; capturing it into a non-inline nested lambda is what triggers the requirement.

saying these in an interview costs you the question

  • Saying crossinline is optional cosmetic style rather than compiler-mandated in these cases
  • Suggesting noinline when the lambda is only invoked, never stored (loses inlining needlessly)
  • Claiming any inline lambda can be passed to a Runnable without a modifier
  • Not recognizing nested lambdas/anonymous objects as triggers
  • Confusing 'called indirectly' with 'called multiple times'

context