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