Swift forEach vs for-in: Differences, Use Cases, and Mistakes

Swift gives you two common ways to iterate over collections: the for-in loop and the forEach method. They can look similar at first, but they behave differently in important ways, especially when you need control flow like break, continue, or early exit. In this article, you will learn how each option works, when to choose one over the other, and which mistakes to avoid in real Swift code.

Quick answer: Use for-in for most loops, especially when you need break, continue, or clearer control flow. Use forEach when you want to apply the same action to every element and do not need to stop or skip the loop in the usual way.

Difficulty: Beginner

You'll understand this better if you know: basic Swift syntax, arrays and dictionaries, and how functions and closures work at a simple level.

1. What Are the Options?

The comparison matters because both for-in and forEach let you process items in a collection, but they are not interchangeable in every situation.

In practice, many Swift developers prefer for-in as the default choice because it is easier to understand and more flexible. forEach is useful when you want to clearly express, “run this operation for every element.”

2. Side-by-Side Comparison

Featurefor-inforEach
TypeLanguage loop syntaxMethod that takes a closure
Works with sequencesYesYes
Supports breakYesNo
Supports continueYesNo direct support
Early exit from surrounding functionYes, with returnNo, return exits only the closure call
Closure syntax requiredNoYes
Readability for simple loopingUsually very clearCan be concise
Best when control flow mattersExcellentPoor fit

3. Key Differences Explained

Loop construct vs method call

for-in is built into the Swift language. It reads naturally and is designed specifically for iteration.

forEach is a method. That means you call it on a sequence and pass a closure that runs once for each element.

Control flow behavior

This is the most important difference. In a for-in loop, you can stop the loop with break, skip the current iteration with continue, or leave the surrounding function with return.

Inside forEach, those usual loop controls do not work the same way because you are inside a closure, not a traditional loop body.

Readability and intent

for-in often communicates “I am looping and may make decisions while looping.”

forEach often communicates “apply this action to every element.” That can be nice when the action is short and there is no branching logic.

Performance expectations

In normal app code, the performance difference is usually not the deciding factor. You should choose based on clarity and control flow needs first. Swift can optimize both forms well in many cases, but for-in is generally the safer readability choice.

4. When to Use Each

Choose based on what the loop needs to do, not just on which syntax looks shorter.

Use for-in when

Use forEach when

A good rule of thumb is: if you are even wondering whether you might need break or continue, start with for-in.

5. Code Examples

Example 1: Basic array iteration with for-in

This example uses a normal loop to print each name in an array.

let names = ["Ava", "Noah", "Mia"]

for name in names {
    print(name)
}

This is the most direct and readable form for basic iteration.

Example 2: Basic array iteration with forEach

This version does the same job, but uses a closure passed to forEach.

let names = ["Ava", "Noah", "Mia"]

names.forEach { name in
    print(name)
}

This is concise and works well when the loop body is small and simple.

Example 3: Stopping early with for-in

If you need to stop when you find a match, for-in is the right tool.

let scores = [72, 88, 95, 67]

for score in scores {
    if score >= 90 {
        print("Found an A-grade score: \(score)")
        break
    }
}

As soon as Swift finds the first score that matches, the loop stops.

Example 4: Skipping values with for-in

When you want to ignore some elements, continue keeps the loop readable.

let numbers = [1, 2, 3, 4, 5]

for number in numbers {
    if number % 2 == 0 {
        continue
    }

    print(number)
}

This prints only odd numbers because even numbers are skipped.

Example 5: Simulating a skip in forEach

You cannot use continue directly in forEach, but you can return from the closure for that element.

let numbers = [1, 2, 3, 4, 5]

numbers.forEach { number in
    if number % 2 == 0 {
        return
    }

    print(number)
}

This skips the current closure body for even numbers, but it does not stop the full iteration.

Example 6: Iterating with index and value

Both styles can work with enumerated() when you need positions.

let tasks = ["Plan", "Build", "Test"]

for (index, task) in tasks.enumerated() {
    print("\(index): \(task)")
}

This is a common and readable pattern when you need both the index and the element.

let tasks = ["Plan", "Build", "Test"]

tasks.enumerated().forEach { index, task in
    print("\(index): \(task)")
}

This also works, though some developers find the for-in version easier to scan.

6. Common Mistakes

Mistake 1: Trying to use break inside forEach

A very common beginner mistake is assuming that forEach behaves exactly like a normal loop.

Problem: break is only valid in a loop or switch statement. A forEach body is a closure, so this code does not compile.

let scores = [72, 88, 95]

scores.forEach { score in
    if score >= 90 {
        break
    }
}

Fix: Use for-in when you need to stop iterating early.

let scores = [72, 88, 95]

for score in scores {
    if score >= 90 {
        print("Stopping at \(score)")
        break
    }
}

The corrected version works because for-in supports normal loop control statements.

Mistake 2: Expecting return in forEach to exit the surrounding function

Inside forEach, return returns from the current closure call, not from the outer function in the same way a loop body might suggest.

Problem: This code looks like it should stop the whole search as soon as it finds a negative value, but it only exits that one closure execution and the iteration continues.

func containsNegative(in numbers: [Int]) -> Bool {
    var found = false

    numbers.forEach { number in
        if number < 0 {
            found = true
            return
        }
    }

    return found
}

Fix: Use for-in if you want to return immediately from the surrounding function.

func containsNegative(in numbers: [Int]) -> Bool {
    for number in numbers {
        if number < 0 {
            return true
        }
    }

    return false
}

The corrected version works because the return belongs to the function itself, not just to a closure call.

Mistake 3: Choosing forEach for complex logic

forEach can become harder to read when you add multiple conditions, nested checks, or state changes.

Problem: This style hides loop intent inside a closure and makes the iteration harder to maintain, especially when later changes need skipping or early exit.

let orders = [120, 0, 85, -1]
var validTotal = 0

orders.forEach { order in
    if order < 0 {
        print("Invalid order found")
        return
    }

    if order == 0 {
        return
    }

    validTotal += order
}

Fix: Rewrite complex iteration as a for-in loop so the control flow is explicit.

let orders = [120, 0, 85, -1]
var validTotal = 0

for order in orders {
    if order < 0 {
        print("Invalid order found")
        continue
    }

    if order == 0 {
        continue
    }

    validTotal += order
}

The corrected version works better because the loop rules are visible and easy to extend.

7. Tradeoffs and Edge Cases

If your goal is not “loop through everything,” consider whether a more specific sequence method describes the job better than either of these iteration styles.

8. Decision Guide

9. Key Points

10. Final Summary

forEach and for-in both iterate over Swift collections, but they solve slightly different problems. forEach is compact and expressive when you want to run one simple action for every element. for-in is more flexible and usually easier to read when your loop needs conditions, skipping, stopping, or early return behavior.

The safest habit for most Swift developers is to default to for-in and use forEach only when its closure-based style genuinely makes the code clearer. As a next step, you may want to compare forEach with other higher-order functions like map, filter, and compactMap so you can choose the right tool for each iteration task.