skip to content

Returns at Labels

Inside a lambda, a bare return exits the enclosing function — a non-local return — while return@forEach only exits the lambda. This trips people up constantly, and interviewers use it to check you know which lambdas even permit a non-local return.

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

questions

5

In Kotlin, what does a bare `return` statement inside a lambda passed to `forEach` do, and how is that different from returning from the lambda alone?

level: juniorimportance: must knowfreq 70%

answer

  1. Bare return = leave the whole fun (non-local)
  2. return@forEach = skip one element (like continue)
  3. Non-local return needs inline function
  4. No bare break for forEach
  5. Label is named after the called function

basics

~10 s

A bare return inside a forEach lambda exits the whole surrounding function, not just the loop step. To skip only the current step you write return@forEach, which leaves the lambda and continues the loop.

solid answer

~40 s

A plain `return` in a lambda is a non-local return: it returns from the nearest enclosing named function (e.g. the method that calls `forEach`), not from the lambda. This works only because `forEach` is an inline function, so the lambda body is copied into the caller and a real `return` is legal there. If you want to exit just the lambda for the current element, use a qualified return `return@forEach`, which acts like `continue` in a normal loop. The label after `@` is the implicit label Kotlin assigns from the function name the lambda is passed to. With a non-inline higher-order function a bare `return` won't compile, forcing the qualified form.

code

kotlin · 9 lines
kotlin
fun firstEven(nums: List<Int>): Int? {
    nums.forEach {
        if (it % 2 != 0) return@forEach  // skip odds, keep looping
        return it                        // non-local: return from firstEven
    }
    return null
}

println(firstEven(listOf(1, 3, 4, 6))) // 4

go deeper

for a junior

Knows bare return leaves the function and return@forEach skips one element.

for a middle

Explains the inline requirement and that there is no break for forEach.

for a senior

Discusses how inlining generates the actual return and when the bare form is rejected.

for a principal

Reasons about API design implications — choosing inline vs non-inline to permit or forbid non-local returns, and readability trade-offs.

## The two kinds of `return` in a lambda Kotlin distinguishes between a **non-local return** and a **local (qualified) return**. - A **bare `return`** inside a lambda is a *non-local return*: it returns from the nearest enclosing **named function** (a `fun`), skipping out of the lambda and everything around it. - A **qualified return** `return@label` returns only from the lambda labelled `label`, leaving the surrounding function running. ```kotlin fun hasNegative(numbers: List<Int>): Boolean { numbers.forEach { if (it < 0) return true // non-local: returns from hasNegative } return false } ``` Here `return true` exits `hasNegative` entirely the moment a negative is seen. ## Why this is even allowed: `inline` `forEach` is declared `inline`. The compiler copies the lambda body straight into the call site, so a `return` there compiles to a real return of the enclosing function. **Non-local returns are only possible from lambdas passed to `inline` functions.** If `forEach` were a normal (non-inline) function, the bare `return` would be a compile error and you'd be forced to qualify it. ## Qualified return — the `continue` analogue To skip just the current element (like `continue` in a `for` loop), qualify the return with the **implicit label**, which Kotlin names after the function the lambda is passed to: ```kotlin fun printPositives(numbers: List<Int>) { numbers.forEach { if (it <= 0) return@forEach // skip this element, keep looping println(it) } } ``` There is no built-in `break` equivalent for `forEach`; to stop early you either use a non-local `return`, switch to a real `for` loop with a labeled `break`, or use `takeWhile`/`firstOrNull`. ## Key terms - **Lambda**: an anonymous function literal like `{ ... }`. - **Non-local return**: a `return` that jumps out past the lambda to a named function. - **`inline`**: a function modifier that pastes the function (and its lambda) into the caller, enabling non-local returns. - **Implicit label**: the auto-generated `@forEach` / `@functionName` label you target with `return@`.

  • Why does a bare `return` compile inside `forEach` but not inside a lambda passed to a custom non-inline function?
    Because `forEach` is `inline`, so the lambda body is inlined into the caller and a real return of the enclosing function is generated. Non-inline lambdas are compiled to separate objects, where a non-local return has no target, so it's a compile error.
  • How do you stop iterating early over a list without a real `for` loop?
    Use a non-local `return` from an enclosing function, or use functions like `firstOrNull`, `find`, `takeWhile`, or `any`/`all` that short-circuit — `forEach` itself has no `break`.

Bare return is leaving the building; return@forEach is just stepping out of the current room and back into the hallway.

saying these in an interview costs you the question

  • Thinking a bare `return` only exits the lambda or the loop
  • Claiming `return@forEach` exits the surrounding function
  • Saying `break` works directly inside a `forEach` lambda
  • Not knowing non-local return requires an inline function
  • Confusing the @label position (writing `@forEach return`)

context

open as a page

What is an implicit label in Kotlin, and how do you give a lambda an explicit label to target with a qualified return?

level: middleimportance: should knowfreq 55%

basics

~20 s

By default Kotlin names a lambda's label after the function it's passed to, so you can write return@forEach. You can override that by writing your own label before the lambda, like loop@ { ... }, and then use return@loop.

open as a page

A developer writes `value?.let { return }` expecting to return early from a helper. What actually happens, and what is the subtle bug when this idiom is misused?

level: middleimportance: should knowfreq 40%

basics

~20 s

let, run, apply, etc. are inline, so a bare return inside their lambda returns from the whole enclosing function — not just from the let block. People expect it to stop only the let, which is the bug.

open as a page

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

level: seniorimportance: should knowfreq 45%

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.

open as a page

Beyond `return@label`, how are labels (`this@label`) used to disambiguate receivers in nested lambdas with receivers, and how does this relate to return labels?

level: seniorimportance: nice to knowfreq 25%

basics

~10 s

The same @label names that target return@label also qualify this@label. In nested blocks with receivers (like apply inside apply), this@outerLabel lets you reach an outer receiver instead of the innermost one.

open as a page