How to Break Out of a Kotlin forEach Loop: Complete Guide with Examples

Use a labeled return (return@forEach) to exit a forEach lambda early, or switch to a traditional for loop if you need a true break statement.

Kotlin’s forEach extension function provides a concise way to iterate over collections, but its lambda-based design prevents the use of traditional break statements. According to the JetBrains/kotlin repository source code, understanding how forEach is implemented reveals why alternative approaches are necessary.

Why You Can't Use a Traditional Break Inside forEach

The forEach function is defined as an inline extension on Iterable<T> in libraries/stdlib/common/src/generated/_Collections.kt at lines 1914-1916:

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

Because forEach accepts a lambda (action: (T) -> Unit), a traditional break statement cannot be used inside that lambda. The Kotlin compiler treats break as an attempt to break from the lambda itself, not from the surrounding iteration (which does not exist as a loop construct inside the lambda).

Method 1: Using a Labeled Return to Exit forEach Early

The most idiomatic way to stop processing elements is using a labeled return (return@forEach). This performs a non-local return from the lambda, effectively skipping the remaining elements without exiting the enclosing function.

val numbers = listOf(1, 2, 3, 4, 5)

numbers.forEach { n ->
    if (n == 3) return@forEach   // exit the lambda early; continue with next element
    println(n)                    // prints 1, 2, then skips 3, prints 4, 5
}

The label @forEach tells the compiler to return only from the lambda, not from the surrounding function. This is the equivalent of a continue statement in a traditional loop.

Method 2: Returning from the Enclosing Function

If you need to abort the entire surrounding function (not just skip remaining elements), you can use an unlabeled return inside the lambda. This works because forEach is declared as an inline function.

fun findFirstEven(numbers: List<Int>): Int? {
    numbers.forEach {
        if (it % 2 == 0) return it   // returns from `findFirstEven`, not just the lambda
    }
    return null
}

When the compiler inlines the forEach call, the return statement becomes a direct return from findFirstEven. This pattern is useful for search operations where you want to exit immediately upon finding a match.

Method 3: Using a Traditional for Loop with Break

When you need true break semantics (exiting the loop completely rather than just returning from a lambda), replace forEach with a traditional for loop:

val items = (1..10).toList()

for (i in items) {
    if (i == 6) break      // true loop-level break
    print("$i ")           // prints 1 2 3 4 5
}
println()

A classic for loop provides native break and continue semantics without requiring labeled returns or inline function considerations. This approach is preferable when you need complex control flow, multiple break conditions, or nested loops.

Performance and Compilation Considerations

The forEach function is marked as inline, meaning the compiler copies the function body (including your lambda) directly into the call site. This eliminates the overhead of creating a function object for the lambda, making forEach performance-equivalent to a manual for loop in most cases.

However, because the lambda is inlined, non-local returns (both labeled and unlabeled) are compiled as direct jumps rather than exception-based stack unwinding. This makes early exit from forEach highly efficient compared to similar patterns in non-inline higher-order functions.

Summary

  • Traditional break does not work inside forEach lambdas because forEach is not a loop construct but a higher-order function.
  • Use return@forEach to skip the remaining elements and continue execution after the forEach block (equivalent to continue).
  • Use unlabeled return inside forEach to exit the enclosing function entirely, leveraging the inline nature of forEach.
  • Use a traditional for loop when you need true break semantics or complex control flow without labeled returns.

Frequently Asked Questions

Can you use break inside a Kotlin forEach?

No, you cannot use a traditional break statement inside a forEach lambda. The forEach function accepts a lambda parameter, and break is only valid within loop constructs like for, while, or do-while. Attempting to use break inside forEach results in a compilation error because the compiler cannot determine which loop to break from.

What is the difference between return and return@forEach?

return@forEach performs a local return from the lambda, causing forEach to proceed to the next element (similar to continue in a regular loop). An unlabeled return inside forEach performs a non-local return from the enclosing function because forEach is an inline function. The unlabeled return exits the entire surrounding function immediately, not just the lambda.

Is forEach inlined by the compiler?

Yes, forEach is declared as an inline function in the Kotlin standard library. The implementation in _Collections.kt shows the inline modifier, which causes the compiler to copy the function body and the lambda directly into the call site. This inlining eliminates the runtime overhead of creating a function object for the lambda and enables non-local returns to work efficiently.

When should I use a for loop instead of forEach?

Use a traditional for loop instead of forEach when you need true break or continue semantics, when working with nested loops where labeled returns become confusing, or when you need to modify the loop variable or index during iteration. for loops provide clearer control flow for complex iteration patterns, while forEach is best suited for simple, side-effect-only operations on each element.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →