skip to content

Explain how `inline`, `noinline`, and `crossinline` interact with non-local returns from lambda parameters.

level: seniorimportance: should knowfreq 45%

answer

  1. inline lambdas → non-local return OK
  2. noinline → object, no non-local return
  3. crossinline → inlined but no non-local return
  4. crossinline = called from another context
  5. Error: 'return is not allowed here'

basics

~20 s

Only lambdas of an inline function can use a bare (non-local) return. Marking a parameter noinline removes that ability. crossinline keeps the lambda inlined but forbids non-local returns, because the lambda might be invoked from another context.

solid answer

~40 s

Non-local returns work because the compiler inlines the lambda body into the caller, so a `return` targets the real enclosing function. In an `inline` function, each lambda parameter is inlined by default and therefore permits non-local return. Adding `noinline` to a parameter makes it a normal lambda object — it can be stored or passed on, but a bare `return` inside it won't compile. `crossinline` is the middle ground: the lambda is still inlined (no object allocated), but you promise it may be called from a different execution context (e.g. inside another lambda, a nested object, or a `Runnable`), where a non-local return would be unsafe. So `crossinline` forbids non-local returns while keeping inlining benefits. The compiler reports 'non-local returns are not allowed' if you violate these.

code

kotlin · 12 lines
kotlin
inline fun guard(predicate: Boolean, crossinline onFail: () -> Unit) {
    if (!predicate) {
        val task = Runnable { onFail() } // different context → crossinline
        task.run()
    }
}

fun use() {
    // guard(false) { return }            // would NOT compile
    guard(false) { return@guard }          // local return: compiles
    println("reached")
}

go deeper

for a junior

Aware that non-local return needs an inline function but fuzzy on the modifiers.

for a middle

Knows noinline blocks non-local returns and that inline lambdas allow them.

for a senior

Clearly distinguishes crossinline (inlined, no non-local return, foreign context) from noinline.

for a principal

Designs inline APIs deliberately choosing modifiers to control allocation, reified access, and return semantics for library callers.

## Why inlining enables non-local return A **non-local return** (a bare `return` in a lambda that exits the *enclosing* function) is only legal when the lambda is inlined: the compiler pastes the lambda body into the call site, so the `return` becomes an ordinary return of the surrounding function. Lambdas compiled as separate function objects have no enclosing function to return from, so the bare form is rejected. ## `inline` Marking a function `inline` inlines the function *and* its lambda parameters at every call site. By default every functional parameter of an inline function is inlined and thus supports non-local returns: ```kotlin inline fun runTwice(block: () -> Unit) { block(); block() } fun demo() { runTwice { return } // OK: non-local return from demo() } ``` ## `noinline` Apply `noinline` to a specific lambda parameter when you need to treat it as a real object — store it, return it, or pass it to a non-inline function. A `noinline` lambda is **not** inlined and therefore **cannot** use a non-local return. ```kotlin inline fun setup(inlined: () -> Unit, noinline stored: () -> Unit) { inlined() // bare return allowed here callbacks += stored // must be noinline to store it } ``` ## `crossinline` Sometimes a lambda is still inlined but is **invoked from a different context** — for example inside an object, a nested lambda, or passed to a `Runnable`. A non-local return there would try to exit a function that may already have returned, which is unsafe. `crossinline` keeps the lambda inlined (no allocation, can still access reified type parameters etc.) but **forbids non-local returns**: ```kotlin inline fun runLater(crossinline block: () -> Unit) { val r = Runnable { block() } // executed elsewhere r.run() } fun demo() { runLater { return } // COMPILE ERROR: 'return' is not allowed here runLater { return@runLater } // OK: local return } ``` ## Summary table - **inlined (default in inline fun)** → non-local return allowed. - **`noinline`** → real object, non-local return forbidden. - **`crossinline`** → still inlined, non-local return forbidden (lambda may run in another context). ## Key terms - **Non-local return**: bare `return` exiting the enclosing function from inside a lambda. - **`inline`**: pastes function + lambdas into the caller. - **`noinline`**: opt a lambda parameter out of inlining. - **`crossinline`**: keep inlining but disallow non-local return.

  • Why can't a `crossinline` lambda use a non-local return even though it's inlined?
    Because it may be executed from a different execution context (an object, nested lambda, or callback) where the enclosing function may have already returned, so jumping out of it would be unsafe — the compiler forbids it.
  • When would you reach for `noinline`?
    When you need to treat the lambda as a first-class value — store it in a field/collection, return it, or pass it to a non-inline function — which requires a real lambda object rather than inlined code.

saying these in an interview costs you the question

  • Saying `crossinline` allows non-local returns
  • Claiming `noinline` lambdas still support non-local return
  • Thinking `crossinline` allocates a lambda object like `noinline`
  • Not connecting non-local return to inlining at all
  • Believing all lambdas everywhere can do a bare return

context