In what situation does the Kotlin compiler require you to mark an inline function's lambda parameter as crossinline?
answer
- Indirect use triggers the requirement
- Runnable / anonymous object / nested lambda capture
- Compiler: 'add crossinline modifier'
- crossinline keeps inline; noinline drops it
- Direct call in body needs nothing
basics
~20 sWhen 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 sBy 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 linesinline 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
Recognizes that passing the lambda into a Runnable forces an extra modifier.
Names the concrete triggers (Runnable, nested lambda, anonymous object) and explains they break non-local returns.
Chooses correctly between crossinline and noinline based on whether the lambda is merely invoked or must be stored.
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'