Question

How to Use break and continue with forEach in Kotlin

kotlinloopsforeachlambda

Question

In Kotlin, iteration helpers such as forEach and repeat are convenient, but break and continue do not seem to work with them the same way they do in normal loops.

For example:

repeat(5) {
    break
}

(1..5).forEach {
    continue@forEach
}

How can you achieve behavior similar to break and continue when using Kotlin's functional iteration style?

I want to mimic ordinary loops as closely as possible while still using functions like forEach and repeat. I believe this may have worked differently in older Kotlin versions, or there may be a special language feature or annotation involved.

For example, I was expecting something like this to be possible:

public inline fun repeat(times: Int, body: (Int) -> Unit) {
    for (index in 0 until times) {
        body(index)
    }
}

Why does this happen, and what is the correct Kotlin approach?

Short Answer

By the end of this page, you will understand why break and continue do not work directly inside Kotlin functions like forEach and repeat, how inline functions and labeled return behave, and when to use a regular for loop instead. You will also learn practical alternatives for skipping items, stopping iteration early, and writing clear loop logic in real Kotlin code.

Concept

In Kotlin, break and continue are loop-control keywords. They only work with actual loop constructs such as:

  • for
  • while
  • do...while

Functions like forEach and repeat may look like loops when you read them, but they are still function calls. Their bodies are lambdas, not loop bodies in the language grammar.

That difference matters:

  • break means: stop the nearest real loop.
  • continue means: skip to the next iteration of the nearest real loop.
  • A lambda passed to forEach is not itself a loop.

So this does not work:

(1..5).forEach {
    break
}

because break can only target a real loop.

What can you do instead?

Kotlin gives you a useful alternative: returns from lambdas.

continue-like behavior

Inside forEach, you can use a labeled return to skip the rest of the current lambda execution and move to the next element:

(1..5).forEach {
    if (it == 3) return@forEach
    println(it)
}

This prints:

1
2
4
5

That behaves like continue.

break-like behavior

There is no direct break inside forEach. If you want to stop iteration early, common options are:

  • use a regular for loop
  • use functions like any, find, firstOrNull, or takeWhile
  • use a labeled return from an outer inline function in specific cases

For most code, if you truly need break, the clearest choice is a normal for loop.

Why inline does not change break

Even though forEach and repeat are often inline, that does not make their lambdas become true loop bodies for break and continue. Inline affects function-call behavior and allows certain kinds of return, but it does not redefine the meaning of loop-control keywords.

Why this matters in real code

Understanding this helps you choose the right tool:

  • Use forEach when you want to perform an action for every item.
  • Use return@forEach when you want continue-like skipping.
  • Use a for loop when you need real break or continue.
  • Use collection operations when your real goal is searching, filtering, or transforming data.

This makes Kotlin code easier to read and avoids fighting the language.

Mental Model

Think of a regular loop as a treadmill with built-in control buttons:

  • continue = skip this step and move to the next one
  • break = stop the treadmill completely

Now think of forEach as hiring a worker to process each item one at a time. You can tell the worker, “stop what you're doing for this item” using return@forEach, but that is not the same as pressing the treadmill's break button. The worker is just inside a function call, not controlling the actual loop keyword machinery.

So:

  • Real loop = you control the loop directly
  • forEach lambda = you control only the current callback execution

That is why return@forEach feels like continue, but there is no direct equivalent of break there.

Take Quiz

Syntax and Examples

Real loop syntax

for (i in 1..5) {
    if (i == 2) continue
    if (i == 4) break
    println(i)
}

forEach with continue-like behavior

Use a labeled return:

(1..5).forEach {
    if (it == 3) return@forEach
    println(it)
}

Explanation

  • When it == 3, return@forEach ends the current lambda call.
  • forEach then moves on to the next item.
  • This is the closest equivalent to continue.

forEach cannot use break

This is invalid:

(1..5).forEach {
    if (it == 3) break
}

Use a regular loop when you need break

for (i in 1..5) {
    if (i == 3) break
    println(i)
}

Use another function when your goal is to stop early

If you are searching for something, use firstOrNull:

val result = (1..5).firstOrNull { it == 3 }
println(result)

If you want to process items only while a condition is true, use takeWhile:

(1..10)
    .takeWhile { it < 5 }
    .forEach { println(it) }

repeat behaves the same way

repeat is also a function, so break is not allowed inside its lambda:

repeat(5) {
    if (it == 2) return@repeat
    println(it)
}

This skips one iteration, similar to continue. But if you need break, use a for loop:

for (i in 0 until 5) {
    if (i == 2) break
    println(i)
}

Step by Step Execution

Consider this example:

(1..5).forEach {
    if (it == 3) return@forEach
    println(it)
}

Step-by-step

  1. 1..5 creates a range: 1, 2, 3, 4, 5
  2. forEach starts calling the lambda for each value.
  3. First value: it = 1
    • it == 3 is false
    • println(1) runs
  4. Second value: it = 2
    • it == 3 is false
    • println(2) runs
  5. Third value: it = 3
    • it == 3 is true
    • return@forEach exits the lambda for this item
    • println(it) is skipped
  6. Fourth value: it = 4
    • condition is false
    • println(4) runs
  7. Fifth value: it = 5
    • condition is false
    • println(5) runs

Output

1
2
4
5

Now compare that with a real break:

for (i in 1..5) {
    if (i == 3) break
    println(i)
}

What happens here?

  1. i = 1 → print 1
  2. i = 2 → print 2
  3. i = 3 → break stops the loop completely
  4. 4 and 5 are never reached

Output

1
2

This shows the key difference:

  • return@forEach skips the current callback
  • break stops the whole loop

Real World Use Cases

Skipping invalid data during processing

val names = listOf("Ana", "", "Ben")

names.forEach {
    if (it.isBlank()) return@forEach
    println(it)
}

Useful when processing user input or CSV rows.

Searching until a match is found

Instead of forcing break into forEach, use a search function:

val userId = listOf(10, 20, 30).firstOrNull { it == 20 }

This is common in APIs and database result handling.

Validating a list of items

val allPositive = listOf(1, 2, 3).all { it > 0 }

This often replaces manual loops that would break on failure.

Processing items until a condition changes

val values = listOf(1, 2, 3, 0, 4, 5)
values.takeWhile { it != 0 }.forEach { println(it) }

Useful in stream-like data processing.

Traditional loop logic in business code

If your logic genuinely needs both continue and break, a standard for loop is usually best:

for (file in files) {
    if (!file.exists()) continue
    if (file.name == "stop.txt") break
    println(file.name)
}
Take Quiz

Real Codebase Usage

In real Kotlin projects, developers usually choose iteration style based on intent.

Common patterns

1. Use forEach for side effects on every item

users.forEach { println(it.name) }

This is good when you simply want to do something for each element.

2. Use return@forEach as a guard clause

users.forEach {
    if (!it.isActive) return@forEach
    sendEmail(it)
}

This is a common pattern for skipping invalid or irrelevant items.

3. Use filter before forEach

users
    .filter { it.isActive }
    .forEach { sendEmail(it) }

This often reads better than putting skip logic inside forEach.

4. Use firstOrNull, find, any, or all instead of manual early exit

val admin = users.find { it.role == "admin" }
val hasErrors = logs.any { it.level == "ERROR" }

This is very common in production Kotlin because the code expresses the goal directly.

5. Use a for loop when loop control matters

for (token in tokens) {
    if (token.isBlank()) continue
    if (token == "END") break
    process(token)
}

This is common in parsers, input processing, and low-level control flow.

Practical guideline

  • Need every element processed? Use forEach
  • Need to skip some elements? Use return@forEach or filter
  • Need to stop early? Use find, any, takeWhile, or a for loop
  • Need classic loop control? Use a real loop

Common Mistakes

Mistake 1: Trying to use break inside forEach

Broken code:

(1..5).forEach {
    if (it == 3) break
}

Why it fails:

  • break only works in real loops.
  • forEach is a function call with a lambda.

How to fix it:

  • Use a for loop, or
  • use a function that matches your intent, such as find or takeWhile

Mistake 2: Expecting return@forEach to stop everything

Broken expectation:

(1..5).forEach {
    if (it == 3) return@forEach
    println(it)
}

Some beginners expect the loop to stop at 3, but it does not. It only skips that one element.

Mistake 3: Using forEach when a normal loop is clearer

Sometimes developers try too hard to keep functional style:

items.forEach {
    if (!isValid(it)) return@forEach
    // many conditions here
}

If the logic becomes control-flow heavy, use:

for (item in items) {
    if (!isValid(item)) continue
    // clearer for complex logic
}

Mistake 4: Confusing local and non-local return

In inline lambdas, plain return may try to return from the enclosing function, not just the lambda.

fun test() {
    listOf(1, 2, 3).forEach {
        if (it == 2) return
    }
    println("This will not run")
}

This returns from test(), not only from forEach.

To return only from the lambda, use a label:

fun test() {
    listOf(1, 2, 3).forEach {
        if (it == 2) return@forEach
        println(it)
    }
    println("This will run")
}

Mistake 5: Forgetting more expressive standard functions

Instead of writing loop logic manually:

var found: Int? = null
for (n in 1..10) {
    if (n > 5) {
        found = n
        break
    }
}

You can often write:

val found = (1..10).firstOrNull { it > 5 }

Comparisons

ConceptWorks with breakWorks with continueBest for
for loopYesYesClassic loop control
while loopYesYesCondition-based repetition
forEachNoNo direct keyword, but return@forEach is similarSide effects for each item
repeatNoNo direct keyword, but return@repeat is similarFixed-count repetition
find / firstOrNullNot neededNot neededStopping when a match is found
any / allNot neededNot neededBoolean checks over collections
takeWhileNot neededNot neededProcessing until a condition becomes false

for vs forEach

FeatureforforEach
Language loop constructYesNo
Supports breakYesNo
Supports continueYesNo
Can skip current itemYes, with continueYes, with return@forEach
Good for complex control flowYesUsually less ideal
Good for concise side-effect codeSometimesYes

Rule of thumb

  • If you need loop control keywords, use for
  • If you want concise per-item processing, use forEach
  • If your goal is search/filter/validation, prefer collection functions over either style
Take Quiz

Cheat Sheet

Quick rules

  • break works only in real loops: for, while, do...while
  • continue works only in real loops
  • forEach and repeat are functions, not loop keywords
  • return@forEach skips the current lambda call
  • return@repeat skips the current lambda call
  • Use for when you need true break or continue

Common patterns

Skip current item in forEach

items.forEach {
    if (shouldSkip(it)) return@forEach
    process(it)
}

Stop early when searching

val match = items.firstOrNull { condition(it) }

Stop processing while condition is true

items.takeWhile { condition(it) }.forEach { process(it) }

Use a real loop for classic control

for (item in items) {
    if (shouldSkip(item)) continue
    if (shouldStop(item)) break
    process(item)
}

Edge cases

  • Plain return inside an inline lambda may return from the outer function
  • return@forEach returns only from the lambda
  • Inline does not make break and continue valid inside lambdas

Best practice

Choose the construct that matches the intent:

  • transform data → map
  • filter data → filter
  • find one item → find / firstOrNull
  • check conditions → any / all
  • side effects on all items → forEach
  • manual loop control → for

FAQ

Can I use break inside forEach in Kotlin?

No. break only works with actual loop statements like for and while, not inside lambdas passed to functions.

How do I simulate continue in Kotlin forEach?

Use a labeled return:

return@forEach

This skips the rest of the current lambda execution.

How do I stop a forEach early in Kotlin?

Usually, you should use a for loop instead. If your goal is searching or checking a condition, use functions like find, firstOrNull, any, or takeWhile.

Does inline allow break and continue inside lambdas?

No. inline allows some return behavior, but it does not make break or continue valid inside function lambdas.

Why does return@forEach not behave like break?

Because it exits only the current lambda call, not the whole iteration structure.

Should I avoid forEach in Kotlin?

No. forEach is useful for simple per-item actions. But if you need classic loop control, a normal for loop is clearer.

What should I use instead of forEach when I want early exit?

Use:

  • for for true break
  • find or firstOrNull for searching
  • any or all for boolean checks
  • takeWhile for bounded processing
Take Quiz

Mini Project

Description

Build a small Kotlin program that processes a list of numbers and demonstrates when to use forEach with return@forEach and when to switch to a normal for loop. This project helps you see the practical difference between skipping the current item and stopping iteration entirely.

Goal

Create a program that skips negative numbers, prints valid numbers, and stops completely when a special stop value is found.

Requirements

  • Create a list of integers containing positive numbers, negative numbers, and a stop value such as 0.
  • Use forEach once to print only non-negative numbers by skipping negatives.
  • Use a normal for loop once to stop processing when the stop value is reached.
  • Print clear labels so the output shows the difference between the two approaches.
Take Quiz

Keep learning

Related questions

View more