Why break and continue Don't Work in Kotlin's forEach (And How to Fix It)
\n\nIf 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!
Classic Example: A Beginner's Mistake
\n\nImagine 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\nval numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n println(it)\n if (it == 3) break // Compilation error!\n}\n\n\nThe 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.
Anatomy of forEach: It's Not a Loop, It's a Function Call
\n\nLet's open the Kotlin standard library and look at the signature of forEach:
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {\n for (element in this) action(element)\n}\n\n\nThe 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.
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.
How return Works in forEach (An Unexpected Twist)
\n\nNow, return inside forEach does work... but not as you might expect. Take a look:
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\nThe 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.
If you want to exit only from forEach, and not from the entire function, use a label:
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\nOutput: 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.