Kotlin has always been celebrated for its concise and expressive syntax. With the release of Kotlin 2.2 (currently available as 2.2.0-RC
), developers get a long-awaited improvement in control flow inside higher-order functions: the stabilization of non-local break
and continue
.
This feature is more than syntactic sugar — it removes boilerplate, clarifies intent, and makes code using inline functions like forEach
, map
, or run
far more intuitive.
In this guide, you’ll learn:
- What non-local
break
andcontinue
mean in Kotlin - Why they matter for real-world projects
- How to use them effectively with examples
- How to enable them in your compiler settings
Let’s dive in.
What Are Non-Local break
and continue
?
In most languages, break
and continue
work only inside traditional loops:
break
→ exits a loop immediately.continue
→ skips the current iteration and moves to the next one.
But in Kotlin, before 2.2, things got tricky when you worked inside inline lambda expressions.
For example:
return@forEach
would only exit the current lambda, not the outer loop.- Developers often needed flags or nested conditionals just to simulate breaking or continuing.
This created unnecessary complexity, especially in data processing pipelines.
The Challenge: Control Flow in Lambdas Before Kotlin 2.2
Here’s how developers had to handle this problem in earlier versions of Kotlin:
fun processDataOldWay(data: List<String>) {
var foundError = false
data.forEach { item ->
if (item.contains("error")) {
println("Error found: $item. Stopping processing.")
foundError = true
return@forEach // Only exits this lambda, not the loop
}
if (item.startsWith("skip")) {
println("Skipping item: $item")
return@forEach
}
println("Processing: $item")
}
if (foundError) {
println("Processing halted due to error.")
} else {
println("All data processed successfully.")
}
}
fun main() {
val data1 = listOf("item1", "item2", "error_item", "item3")
processDataOldWay(data1)
println("---")
val data2 = listOf("itemA", "skip_this", "itemB")
processDataOldWay(data2)
}
// OUTPUT //
Processing: item1
Processing: item2
Error found: error_item. Stopping processing.
Processing: item3
Processing halted due to error.
---
Processing: itemA
Skipping item: skip_this
Processing: itemB
All data processed successfully.
Problems with this approach:
- Extra flags (
foundError
) to track state. - Logic is harder to read and maintain.
- Code intent is obscured by boilerplate.
The Solution: Non-Local Break and Continue in Kotlin 2.2
With Kotlin 2.2, non-local break
and continue
finally work inside inline functions.
Because inline lambdas are compiled into the calling scope, these keywords now behave exactly as you’d expect inside normal loops.
Here’s the updated version:
fun processDataNewWay(data: List<String>) {
for (item in data) {
if (item.contains("error")) {
println("Error found: $item. Stopping processing.")
break // Non-local break: exits immediately
}
if (item.startsWith("skip")) {
println("Skipping item: $item")
continue // Non-local continue: cleanly skips iteration
}
println("Processing: $item")
}
println("--- Finished processing data block.")
}
fun main() {
println("Processing data1:")
processDataNewWay(listOf("item1", "item2", "error_item", "item3"))
println("\nProcessing data2:")
processDataNewWay(listOf("itemA", "skip_this", "itemB", "itemC"))
}
// OUTPUT //
Processing data1:
Processing: item1
Processing: item2
Error found: error_item. Stopping processing.
--- Finished processing data block.
Processing data2:
Processing: itemA
Skipping item: skip_this
Processing: itemB
Processing: itemC
--- Finished processing data block.
What’s better now:
- No flags or labels needed.
- Direct, intuitive flow control.
- Clearer intent and easier debugging.
Enabling Non-Local Control Flow in Kotlin
To use this feature, add the compiler flag to your Gradle build file:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
freeCompilerArgs.add("-Xnon-local-break-continue")
}
}
Note:
- Required in Kotlin
2.2.0-RC
. - Likely optional in the stable Kotlin 2.2 release, once the feature is fully stabilized.
Why This Matters for Developers
Non-local break
and continue
aren’t just about syntactic sugar—they bring real productivity gains:
- Cleaner higher-order function pipelines → easier reasoning about loops and early exits.
- Reduced cognitive load → no need to remember when
return@label
applies. - Improved maintainability → intent matches the code’s structure.
This makes Kotlin more developer-friendly and strengthens its position as a modern, pragmatic language for both backend and Android development.
FAQs: Kotlin 2.2 Non-Local Break and Continue
Q1: What is a non-local break in Kotlin?
A non-local break
allows you to exit an enclosing loop from inside an inline lambda, not just the lambda itself.
Q2: What is a non-local continue in Kotlin?
A non-local continue
skips the current iteration of the enclosing loop even when inside a lambda.
Q3: Do I need a compiler flag to use this feature?
Yes, in Kotlin 2.2.0-RC
you must enable -Xnon-local-break-continue
. From the stable release onward, this may be enabled by default.
Q4: Where is this most useful?
This is especially powerful in collection processing, functional transformations, and inline control structures like forEach
, map
, or run
.
Q5: Does this replace return labels?
Not entirely. return@label
still has its uses, but in many cases, non-local break
and continue
eliminate the need for them.
Conclusion
The stabilization of non-local break
and continue
in Kotlin 2.2 is a big quality-of-life improvement for developers. It simplifies control flow inside lambdas, reduces boilerplate, and makes code more expressive.
If your codebase relies heavily on higher-order functions, enabling this feature will help you write cleaner, more natural Kotlin.
Kotlin continues to evolve — not just by adding features, but by refining the language to match how developers actually write code. And non-local control flow is a great example of that.