skip to content

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%

answer

  1. Both ban non-local returns
  2. crossinline = inlined, can't store
  3. noinline = not inlined, can store/return
  4. noinline reintroduces an allocation
  5. Can mix all three in one function

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.

solid answer

~50 s

Both modifiers appear on lambda parameters of an inline function and both disable non-local returns. The difference is inlining and storability. crossinline: the lambda body is still inlined at the call site (no function object allocated on the simple path), but you cannot keep it as a value — it only exists at call sites. noinline: the lambda is compiled as an ordinary Function object; it is not inlined, so you can store it in a field, put it in a collection, return it, or pass it to a non-inline API. Choose crossinline when you invoke the lambda in another execution context (Runnable, nested lambda) but never need to hold it; choose noinline when you must treat the lambda as a first-class value. Mixing is allowed: one inline function can have a plain lambda, a crossinline one, and a noinline one.

code

kotlin · 9 lines
kotlin
inline fun build(
    direct: () -> Unit,                 // non-local return OK
    crossinline indirect: () -> Unit,   // inlined, no non-local return
    noinline stored: () -> Unit         // real object: storable
) {
    direct()
    Runnable { indirect() }.run()
    laterHandlers += stored
}

go deeper

for a junior

States that crossinline stays inlined while noinline becomes a normal object you can store.

for a middle

Builds the full comparison table including the shared non-local-return ban and storability.

for a senior

Argues the performance trade-off (allocation with noinline) and picks per parameter accordingly.

for a principal

Designs inline APIs deliberately mixing plain/crossinline/noinline params to balance inlining, return semantics, and value capture for callers.

## Shared trait Both `crossinline` and `noinline` **forbid non-local returns** from the lambda. A bare `return` that would exit the caller is not allowed for either; only `return@label` (local) is. ## crossinline — inlined, not storable With `crossinline` the compiler still **inlines** the lambda body into the call site. There is no standalone function object to grab on the simple path, so you **cannot store it as a value** (no assigning it to a field, putting it in a list, or returning it). You can only **invoke** it — including from an indirect context like a `Runnable` or nested lambda. ```kotlin inline fun onClick(crossinline handler: () -> Unit) { button.setOnClickListener(View.OnClickListener { handler() }) // invoked indirectly } ``` ## noinline — a real object, storable With `noinline` the lambda is **not inlined**; it becomes a normal `kotlin.Function` instance. That means you can do everything you'd do with a value: store it, return it, pass it to a non-inline function. ```kotlin inline fun register(tag: String, noinline callback: () -> Unit) { handlers[tag] = callback // stored for later -> must be noinline } ``` ## Side-by-side | Aspect | plain inline lambda | crossinline | noinline | |---|---|---|---| | Inlined at call site | yes | yes | no | | Non-local return allowed | yes | no | no | | Can be stored/returned as a value | no | no | yes | | Typical trigger | direct call in body | indirect invocation | needs to be kept as a value | ## Mixing modifiers A single inline function can mix all three: ```kotlin inline fun build( direct: () -> Unit, crossinline indirect: () -> Unit, noinline stored: () -> Unit ) { /* ... */ } ``` ## Performance angle `crossinline` preserves the main reason to use `inline` — no per-call lambda allocation — while `noinline` reintroduces an object allocation for that parameter. So prefer `crossinline` when you only need to invoke the lambda, and reach for `noinline` only when value semantics are genuinely required. ## Keywords `inline`, `crossinline`, `noinline`, `kotlin.Function`, non-local `return`, SAM conversion.

  • Which one allocates a function object?
    noinline does, because the lambda becomes a real kotlin.Function instance. crossinline keeps the body inlined and avoids that allocation on the simple path.
  • Can a crossinline lambda be returned from the function?
    No. It is not a value you can hold, so it cannot be returned. You would need noinline for that.

saying these in an interview costs you the question

  • Saying crossinline lambdas can be stored in fields
  • Claiming noinline lambdas are still inlined
  • Stating only one of them forbids non-local returns (both do)
  • Asserting you cannot mix the modifiers in one function
  • Ignoring the allocation cost difference between the two

context