break and continue in Kotlin forEach: How It Works Under the Hood

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why break and continue Don't Work in Kotlin's forEach (And How to Fix It)

\n\n

If you've ever written Kotlin and tried to exit a forEach loop early using break, you've most likely encountered red underlining in your IDE and a compilation error. Sound familiar? You're not alone — this is one of the most popular questions about Kotlin on Stack Overflow, with thousands of views. In this article, we won't just show you how to work around the limitation; we'll also look under the hood: why it's done this way, how return works inside a lambda, and what alternatives exist. Let's go!

\n\n

Classic Example: A Beginner's Mistake

\n\n

Imagine you have a list of numbers and you need to print only the first three elements to the screen. Intuitively, you'd want to write this:

\n\n
val numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n    println(it)\n    if (it == 3) break // Compilation error!\n}\n
\n\n

The compiler will give an error: 'break' and 'continue' are only allowed inside a loop. Why? After all, forEach seems like a loop, right? The answer lies in how forEach is actually structured.

\n\n

Anatomy of forEach: It's Not a Loop, It's a Function Call

\n\n

Let's open the Kotlin standard library and look at the signature of forEach:

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

The key word here is inline. This means that at compile time, the function body is inserted directly at the call site. But still, forEach is a higher-order function that takes another function (a lambda) as an argument. And a lambda is not a loop code block; it's an ordinary function object. break and continue only make sense inside for, while, or do-while constructs, not inside an arbitrary function.

\n\n

In other words, when you write numbers.forEach { ... }, you are not creating a loop — you are calling a method that itself uses a for loop internally. The compiler cannot "pass through" a break from your lambda to the outer loop because the lambda is a separate entity. This is not a bug; it's by design.

\n\n

How return Works in forEach (An Unexpected Twist)

\n\n

Now, return inside forEach does work... but not as you might expect. Take a look:

\n\n
fun main() {\n    val numbers = listOf(1, 2, 3, 4, 5)\n    numbers.forEach {\n        if (it == 3) return // Exits main()!\n        println(it)\n    }\n    println("This line will not execute")\n}\n
\n\n

The output will be: 1 2. The return here is a non-local return. It terminates not just the lambda, but the entire outer function (main). This works because forEach is declared as inline. The compiler inlines the lambda code directly into the body of main, and the return then refers to the nearest declared fun, which is main.

\n\n

If you want to exit only from forEach, and not from the entire function, use a label:

\n\n
fun main() {\n    val numbers = listOf(1, 2, 3, 4, 5)\n    numbers.forEach loop@{\n        if (it == 3) return@loop\n        println(it)\n    }\n    println("This line will execute")\n}\n
\n\n

Output: 1 2 and This line will execute. The label loop@ tells the compiler: "exit the lambda, but not main." Essentially, this is an analog of continue — we skip the current iteration and move on to the next element.

\n\n

How to Emulate break and continue in forEach<

Blogs

Book Recommendations