Swift forEach Explained: Iterating with Closures Clearly

Swift’s forEach method lets you iterate over a collection by passing a closure that runs once for each element. It is concise and expressive, but it behaves differently from a normal for-in loop in important ways. In this article, you will learn how forEach works, how to write it correctly, when it is a good choice, and when a regular loop is better.

Quick answer: In Swift, forEach is a method on sequences and collections that executes a closure for every element. Use it for simple, readable iteration, but use for-in when you need break, continue, or early loop control.

Difficulty: Beginner

Helpful to know first: You’ll understand this better if you know basic Swift syntax, how arrays and dictionaries store values, and what a closure is at a simple level.

1. What Is forEach?

forEach is a method provided by Swift sequences and collections. Instead of writing a loop body directly, you pass a closure that Swift calls for each element in order.

For beginners, the main idea is simple: forEach says “do this action for every item.”

If you have already used for-in, think of forEach as a more functional style of iteration. The biggest comparison to remember is this: forEach is great for straightforward work on every element, while for-in gives you more control over the loop itself.

2. Why forEach Matters

forEach matters because it gives you a compact and expressive way to iterate through data. In real code, that often means fewer temporary names, less visual noise, and a clearer “apply this action to each item” intent.

It is especially useful when:

It is less suitable when:

So forEach matters not because it replaces every loop, but because it is a useful tool for a specific style of iteration.

3. Basic Syntax or Core Idea

Basic array syntax

Here is the minimal working form of forEach with an array. The closure receives each value in the array.

let numbers = [1, 2, 3]

numbers.forEach { number in
    print(number)
}

This prints each element in order. The parameter name number is the current item being processed.

How to read it

You can read the code as: “for each number in numbers, print the number.”

The parts are:

Single-expression closure shorthand

When the closure is very short, you can use shorthand argument names. This is valid Swift, though beginners often find the named version easier to read.

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

names.forEach {
    print($0)
}

This version works the same way, but $0 refers to the first closure parameter automatically.

4. Step-by-Step Examples

Example 1: Printing every value in an array

This is the most direct use of forEach. You have a list of items and want to perform the same action for each one.

let cities = ["Paris", "Tokyo", "Nairobi"]

cities.forEach { city in
    print("City: \(" + city + ")")
}

The closure runs three times, once for each city. This is a clean choice when you only need a simple side effect like printing or logging.

Example 2: Working with a range

forEach also works on ranges because ranges are sequences in Swift.

(1...5).forEach { value in
    print("Count: \(" + String(value) + ")")
}

This runs the closure for values 1 through 5. It is useful when you want a fixed number of repeated actions.

Example 3: Iterating through a dictionary

When you use forEach on a dictionary, each element is a key-value pair.

let scores = ["Ana": 92, "Ben": 85, "Cara": 99]

scores.forEach { entry in
    print("\(" + entry.key + ": \(" + String(entry.value) + ")")
}

This gives you one dictionary element at a time. Remember that dictionary order should not be relied on unless you explicitly create sorted output.

Example 4: Using enumerated() with forEach

If you need both the index and the value, combine enumerated() with forEach.

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

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

This is useful when display order matters or when you need numbered output. The closure receives both values because enumerated() produces a sequence of index-element pairs.

5. Practical Use Cases

6. Common Mistakes

Mistake 1: Expecting break to work inside forEach

A very common beginner mistake is treating forEach exactly like a for-in loop. Although both iterate, forEach is a method that executes a closure, so loop control keywords do not behave the same way.

Problem: break cannot be used to stop a forEach closure the way it stops a normal loop. If you need early exit, this is the wrong tool.

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

numbers.forEach { number in
    if number == 3 {
        break
    }
    print(number)
}

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

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

for number in numbers {
    if number == 3 {
        break
    }
    print(number)
}

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

Mistake 2: Thinking return exits the outer function

Inside a forEach closure, return exits only that closure call, not the surrounding function. Many developers expect it to stop the whole operation.

Problem: This code suggests that finding an empty string should stop the enclosing function, but return only leaves the current closure execution, so later elements still run.

func printNames(_ names: [String]) {
    names.forEach { name in
        if name.isEmpty {
            return
        }
        print(name)
    }

    print("Done")
}

Fix: If you need to stop processing completely, use for-in and return from the outer function directly.

func printNames(_ names: [String]) {
    for name in names {
        if name.isEmpty {
            return
        }
        print(name)
    }

    print("Done")
}

The corrected version works because the return is now inside the function body’s loop, not inside a separate closure scope.

Mistake 3: Using forEach when a transformed result is needed

forEach is for performing actions, not for building a new collection automatically. Beginners sometimes expect it to return transformed values.

Problem: This code expects forEach to produce a new array, but forEach returns Void, so there is no transformed result.

let numbers = [1, 2, 3]

let doubled = numbers.forEach { number in
    number * 2
}

Fix: Use map when you want to create a new array from each element.

let numbers = [1, 2, 3]

let doubled = numbers.map { number in
    number * 2
}

print(doubled)

The corrected version works because map returns a new collection containing each transformed value.

7. Best Practices

Practice 1: Use forEach only for straightforward side effects

forEach is most readable when the closure does one simple thing, such as printing, logging, or updating a small piece of state.

let messages = ["Start", "Loading", "Done"]

messages.forEach { message in
    print(message)
}

This works well because the intent is obvious: perform the same simple action for every item.

Practice 2: Prefer named closure parameters over $0 for learning and clarity

Shorthand syntax is compact, but a named parameter is often easier to understand, especially when the closure body spans multiple lines.

let prices = [9.99, 14.50, 20.00]

prices.forEach { price in
    print("Price: \(" + String(price) + ")")
}

This style is usually clearer than relying on $0, especially for beginners and team code.

Practice 3: Switch to for-in when control flow becomes important

If your iteration needs skipping, stopping, nested branching, or clear early exits, a regular loop is often easier to maintain.

let values = [3, 6, 9, 12]

for value in values {
    if value == 9 {
        break
    }
    print(value)
}

This is a best practice because code should match the job. forEach is elegant, but not at the cost of control and clarity.

8. Limitations and Edge Cases

9. Practical Mini Project

This mini project prints a numbered shopping list, calculates a running total, and highlights expensive items. It shows a realistic use of forEach for side effects.

struct Item {
    let name: String
    let price: Double
}

let items = [
    Item(name: "Notebook", price: 4.50),
    Item(name: "Headphones", price: 29.99),
    Item(name: "Pen", price: 1.20)
]

var total: Double = 0.0

print("Shopping List")

items.enumerated().forEach { index, item in
    print("\(" + String(index + 1) + ". \(" + item.name + " - $\(" + String(item.price) + ")")

    if item.price > 20 {
        print("  Premium item")
    }

    total += item.price
}

print("Total: $\(" + String(total) + ")")

This example uses enumerated() to show item numbers, uses forEach to process each item, and updates a running total outside the closure. It is a good example of the kind of side-effect-driven task that forEach handles well.

10. Key Points

11. Practice Exercise

Try this exercise to confirm that you understand how forEach works.

Expected output: Each temperature should be printed, followed by the average.

Hint: Declare a mutable variable with var before the forEach call, then add each temperature inside the closure.

let temperatures = [18.5, 21.0, 19.5, 23.0, 20.0]
var sum: Double = 0.0

temperatures.forEach { temperature in
    print("Temperature: \(" + String(temperature) + ")")
    sum += temperature
}

let average = sum / Double(temperatures.count)
print("Average: \(" + String(average) + ")")

12. Final Summary

Swift’s forEach method is a clean way to iterate with closures when your goal is simple, repeated action on every element. It works well with arrays, dictionaries, ranges, and other sequences, and it becomes especially readable when paired with trailing closure syntax.

The most important thing to remember is that forEach is not the same as for-in. If you need loop control such as break, more obvious branching, or early exit from a function, a regular loop is usually the better choice. If you need to produce a new collection, use tools like map instead.

Once you are comfortable with forEach, a useful next step is learning how it differs from other sequence methods such as map, filter, and reduce. That will help you choose the right iteration tool for each task.