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.

  • It works on types such as arrays, sets, dictionaries, and ranges.
  • It takes one closure argument.
  • The closure receives each element one at a time.
  • It is often used with trailing closure syntax for readability.
  • It is not a perfect replacement for for-in because loop control works differently.

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:

  • You want to print, transform, inspect, or trigger a side effect for each element.
  • You want code that reads like a sequence of operations.
  • You are already working with closures and higher-order methods such as map, filter, or sorted.

It is less suitable when:

  • You need to stop early after finding a match.
  • You need continue to skip to the next loop iteration in the usual loop sense.
  • You need complex branching that becomes hard to read inside a closure.

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

Basic array syntax

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

Swift
let numbers = [1, 2, 3]

numbers.forEach { number in
    print(number)
}
Output✓ Tested · Swift 6.4
1
2
3

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:

  • numbers: the sequence being iterated
  • forEach: the method that performs iteration
  • { number in ... }: the closure run for each element

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.

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

names.forEach {
    print($0)
}
Output✓ Tested · Swift 6.4
Ava
Liam
Noah

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.

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

cities.forEach { city in
    print("City: \(city)")
}
Output✓ Tested · Swift 6.4
City: Paris
City: Tokyo
City: Nairobi

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.

Swift
(1...5).forEach { value in
    print("Count: \(value)")
}
Output✓ Tested · Swift 6.4
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5

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.

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

scores.forEach { entry in
    print("\(entry.key): \(entry.value)")
}
Output✓ Tested · Swift 6.4
Ben: 85
Cara: 99
Ana: 92

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.

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

tasks.enumerated().forEach { index, task in
    print("Step \(index + 1): \(task)")
}
Output✓ Tested · Swift 6.4
Step 1: Plan
Step 2: Build
Step 3: Test

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

  • Printing or logging every item in an array of values during debugging.
  • Applying a side effect to each element, such as updating counters or collecting statistics.
  • Displaying numbered output by combining enumerated() with forEach.
  • Processing dictionary entries when you want to inspect keys and values together.
  • Running a simple action over a range, such as repeating a test setup a fixed number of times.
  • Writing clear “do this for each item” code when early exit is not needed.

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.

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

numbers.forEach { number in
    if number == 3 {
        break
    }
    print(number)
}
Compiler output✓ Tested · Swift 6.4
error: unlabeled 'break' is only allowed inside a loop or switch, a labeled break is required to exit an if or do

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

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

for number in numbers {
    if number == 3 {
        break
    }
    print(number)
}
Output✓ Tested · Swift 6.4
1
2

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.

Swift
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.

Swift
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.

Swift
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.

Swift
let numbers = [1, 2, 3]

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

print(doubled)
Output✓ Tested · Swift 6.4
[2, 4, 6]

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.

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

messages.forEach { message in
    print(message)
}
Output✓ Tested · Swift 6.4
Start
Loading
Done

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.

Swift
let prices = [9.99, 14.50, 20.00]

prices.forEach { price in
    print("Price: \(price)")
}
Output✓ Tested · Swift 6.4
Price: 9.99
Price: 14.5
Price: 20.0

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.

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

for value in values {
    if value == 9 {
        break
    }
    print(value)
}
Output✓ Tested · Swift 6.4
3
6

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

  • forEach cannot use break or continue like a normal for-in loop.
  • return inside the closure only exits the current closure execution, not the outer function.
  • forEach returns Void, so it is not for creating transformed collections. Use map, compactMap, or filter instead.
  • Dictionary iteration order should not be relied on unless you sort the data first.
  • With complex logic, nested conditions inside the closure can become harder to read than a standard loop.
  • If you see “cannot convert value of type '()'” in code using forEach, you may be trying to assign its result as though it returns a value.

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.

Swift
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("\(index + 1). \(item.name) - $\(item.price)")

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

    total += item.price
}

print("Total: $\(total)")
Output✓ Tested · Swift 6.4
Shopping List
1. Notebook - $4.5
2. Headphones - $29.99
  Premium item
3. Pen - $1.2
Total: $35.69

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

  • forEach runs a closure once for every element in a sequence.
  • It is best for simple, readable actions performed on each item.
  • Use trailing closure syntax to keep the code concise.
  • Choose for-in instead when you need break, continue, or early exit from the surrounding function.
  • forEach returns Void, so it does not build a new collection.
  • Use map for transformation and filter for selection, not forEach.

11. Practice Exercise

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

  • Create an array of five temperatures.
  • Use forEach to print each temperature.
  • Keep a running sum in a separate variable.
  • After the loop, print the average temperature.

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.

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

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

let average = sum / Double(temperatures.count)
print("Average: \(average)")
Output✓ Tested · Swift 6.4
Temperature: 18.5
Temperature: 21.0
Temperature: 19.5
Temperature: 23.0
Temperature: 20.0
Average: 20.4

Where AI Coding Assistants Get This Wrong

Patterns AI tools commonly produce for this topic, and what to use instead.

Often suggested: Writes ids.forEach { id in await store.load(id) } when converting callback code to async/await.

Instead: Use for id in ids { await store.load(id) } for sequential work, or withTaskGroup(of:) / withThrowingTaskGroup(of:) when the calls should run concurrently.

forEach takes a synchronous closure, so the async version doesn't compile.

Often suggested: Uses names.forEach { Text($0) } inside a SwiftUI body or VStack.

Instead: Use the ForEach view: ForEach(names, id: \.self) { Text($0) }, or use Identifiable elements and drop id:.

forEach returns Void, so the builder fails with "type '()' cannot conform to 'View'".

Often suggested: Puts return inside forEach to stop searching once a match is found, or sets a found flag and keeps iterating.

Instead: Use first(where:), contains(where:) or firstIndex(where:), or a for-in loop with return or break.

return only ends the current closure call, so the rest of the sequence is still visited.

Check Your Understanding

Pick an answer for each question, then check your score.

1. What does forEach return?
Show answer

Answer: Nothing (Void). forEach only runs a closure for each element. Use map to build a new collection.

2. Inside a forEach closure, what does return do?
Show answer

Answer: Skips to the next element, like continue in a for-in loop. return only exits the closure for the current element; the loop keeps going.

3. You need to stop looping as soon as you find a match. What should you use?
Show answer

Answer: A for-in loop with break. break isn't allowed inside forEach, and return doesn't stop it, so use for-in.

4. You want a new array with every number doubled. Which method fits best?
Show answer

Answer: map. map transforms each element into a new array. filter selects elements; forEach builds nothing.

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.