Swift forEach: Prefer forEach Only for Side Effects

Swift's forEach method is a convenient way to run the same closure for every element in a collection. It is useful when you want to perform an action for its side effects, but it is usually the wrong choice when you need control flow, early exit, or a transformed result.

Quick answer: Use forEach when you want to do something for each element, such as printing, logging, or updating external state. Prefer a for-in loop when you need break, continue, or clearer iteration.

Difficulty: Beginner

You'll understand this better if you know: basic Swift collections, closures, and the difference between returning a value and performing an action.

1. What Is forEach?

forEach is a method on Swift sequences and collections that calls a closure once for every element. The closure receives each element in order, and the method itself returns Void.

That last point is why the rule of thumb matters: if you are using forEach to build a new collection or to search for a value, there is usually a better tool.

2. Why forEach Matters

forEach can make code read like a list of actions applied to each element. That can be nice when the intent is simple, such as printing, writing to a file, or notifying another object.

However, many beginners reach for it when they really want a loop with control flow. In those cases, forEach can make code harder to understand because it hides the fact that you are not returning a result and you cannot stop early in the usual way.

Prefering forEach only for side effects helps keep code readable and prevents subtle bugs where a developer expects loop behavior that forEach does not provide.

3. Basic Syntax or Core Idea

The minimal form of forEach uses a closure that receives each element:

numbers.forEach { number in
    print(number)
}

This means: take every value in numbers, bind it to number, and run the closure body.

How the closure works

The closure you pass to forEach can use the element, update outside state, or call other functions. Because forEach ignores any return value from the closure, it is not a good fit for producing a new array.

What the method does not do

forEach does not change the collection by itself. It also does not let you leave the loop early with break. If you need those behaviors, use for-in instead.

4. Step-by-Step Examples

Example 1: Printing values

A straightforward side effect is printing each element. This is one of the clearest uses of forEach.

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

names.forEach { name in
    print("Hello, \(name)")
}

This code performs an action for each name. It does not create a new array or change the original one.

Example 2: Logging items conditionally

You can use forEach when the main purpose is to send each item to a logger or debugging output.

let tasks = ["download", "parse", "save"]

tasks.forEach { task in
    if task.hasPrefix("s") {
        print("Important task: \(task)")
    }
}

The closure still runs for every item, but only some items trigger the side effect.

Example 3: Updating external state

Sometimes a closure updates a value outside itself, such as a running total. This is a side effect, so forEach can be reasonable.

let prices = [12, 8, 5]
var total = 0

prices.forEach { price in
    total += price
}

print(total)

This works because the goal is to update total, not to create a transformed collection.

Example 4: Comparing with a for-in loop

Here is the same iteration written as a for-in loop. Notice how the loop gives you more control.

for price in prices {
    if price < 10 {
        continue
    }
    print(price)
}

If you need to skip values or stop early, the for-in loop is clearer and more capable.

5. Practical Use Cases

These are all cases where the important outcome is the action, not a returned collection.

6. Common Mistakes

Mistake 1: Using forEach to build a new array

Many beginners try to use forEach when they want to transform values. That often leads to extra mutable state and less readable code.

Problem: The code below uses forEach to append transformed values into another array. It works, but it is harder to read than a transformation method designed for that job.

let numbers = [1, 2, 3]
var doubles: [Int] = []

numbers.forEach { number in
    doubles.append(number * 2)
}

Fix: Use map when the goal is to create a new array from each element.

let numbers = [1, 2, 3]
let doubles = numbers.map { number in
    number * 2
}

The corrected version is better because it expresses transformation directly instead of relying on side effects.

Mistake 2: Expecting break to stop forEach

forEach is not a full loop statement, so it does not support break in the way a for-in loop does.

Problem: Trying to use break inside forEach causes a compile-time error because the closure is not a loop body.

let scores = [10, 22, 31]

scores.forEach { score in
    if score > 20 {
        break
    }
    print(score)
}

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

let scores = [10, 22, 31]

for score in scores {
    if score > 20 {
        break
    }
    print(score)
}

The for-in version works because break is part of the language's loop syntax.

Mistake 3: Using return to mean “return from the loop”

Inside forEach, return exits only the closure for the current element. It does not stop the whole iteration.

Problem: This code looks like it should stop when it finds an even number, but it keeps processing later elements because return only leaves the closure body for that one item.

let values = [1, 3, 4, 6]

values.forEach { value in
    if value % 2 == 0 {
        return
    }
    print(value)
}

Fix: Use a loop with break, or use a searching method if your real goal is to find an element.

for value in values {
    if value % 2 == 0 {
        break
    }
    print(value)
}

The corrected version matches the intended control flow, so later elements are not processed once the condition is met.

7. Best Practices

Practice 1: Use forEach when the result is intentionally discarded

If the output of the iteration is not needed, forEach can communicate that the purpose is the side effect itself.

let ids = ["A12", "B45", "C98"]

ids.forEach { id in
    print("Processing \(id)")
}

This is a good fit because the code is about doing something to each item, not producing a transformed value.

Practice 2: Prefer for-in when you need control flow

When the iteration needs to stop, skip values, or nest more complex logic, a for-in loop is usually clearer.

for id in ids {
    if id == "B45" {
        break
    }
    print(id)
}

This version is better because it makes the control flow explicit.

Practice 3: Avoid side effects when a return value would be better

If you are computing a new collection, count, or derived value, use the collection method that returns that result instead of writing to external mutable variables.

let amounts = [5, 10, 15]

let filtered = amounts.filter { amount in
    amount > 8
}

This keeps the data flow easier to test and reason about than mutating an external array inside forEach.

8. Limitations and Edge Cases

These limitations are not bugs; they are part of the method's design. The method is intentionally simple.

9. Practical Mini Project

Let's build a tiny command-line report that prints each product name and calculates the total inventory value as a side effect.

struct Product {
    let name: String
    let price: Int
    let quantity: Int
}

let inventory = [
    Product(name: "Notebook", price: 3, quantity: 10),
    Product(name: "Pen", price: 2, quantity: 25),
    Product(name: "Folder", price: 4, quantity: 8)
]

var totalValue = 0

inventory.forEach { product in
    let lineValue = product.price * product.quantity
    totalValue += lineValue
    print("\(product.name): $\(lineValue)")
}

print("Total inventory value: $\(totalValue)")

This example is appropriate for forEach because the side effects are the real output: printing each line and updating a total.

10. Key Points

11. Practice Exercise

Expected output: Five uppercase city names printed, followed by the count of cities containing a.

Hint: Use print and a mutable counter, but do not try to return values from forEach.

let cities = ["Paris", "Tokyo", "Lima", "Oslo", "Cairo"]
var countWithA = 0

cities.forEach { city in
    let uppercased = city.uppercased()
    print(uppercased)

    if city.lowercased().contains("a") {
        countWithA += 1
    }
}

print("Cities containing a: \(countWithA)")

12. Final Summary

forEach is a clear and useful tool when you want to perform an action for each element in a collection. Its strength is simplicity: it reads well when the code is doing side effects such as printing, logging, or updating external state.

Its weakness is control flow. If you need to stop early, skip values, or produce a transformed result, a for-in loop, map, or another collection method is usually a better fit. Keeping that distinction in mind will make your Swift code easier to read and less error-prone.

If you want to continue learning, compare forEach with map, filter, and for-in next, since those are the methods and statements most often confused with it.