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.
- for-in is a language loop construct.
- forEach is a method on sequences and collections that takes a closure.
- for-in gives you normal loop control such as break and continue.
- forEach is often concise, but it changes how control flow works.
- Both can read elements one by one, but readability and behavior differ.
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
| Feature | for-in | forEach |
|---|---|---|
| Type | Language loop syntax | Method that takes a closure |
| Works with sequences | Yes | Yes |
| Supports break | Yes | No |
| Supports continue | Yes | No direct support |
| Early exit from surrounding function | Yes, with return | No, return exits only the closure call |
| Closure syntax required | No | Yes |
| Readability for simple looping | Usually very clear | Can be concise |
| Best when control flow matters | Excellent | Poor 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
- You need break to stop early.
- You need continue to skip some elements.
- You want clear, familiar loop control.
- You are writing logic with conditions, nested loops, or early returns.
- You want the most beginner-friendly and maintainable option.
Use forEach when
- You want to perform the same simple action for every element.
- You do not need to stop early.
- You are already working in a closure-heavy style.
- The body is short and readable.
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
- forEach always visits every element unless the program throws an error or stops for another reason outside normal loop control.
- return inside forEach acts more like skipping the current iteration than ending the whole loop.
- If you need to search for a matching element, methods like contains(where:), first(where:), or allSatisfy(_:) may express your intent better than either for-in or forEach.
- When mutating an external variable inside forEach, the code can become less functional and less clear than expected.
- For dictionaries, both forms work, but remember that dictionary iteration order should not be relied on unless your logic explicitly handles ordering.
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
- If you need break, use for-in.
- If you need continue, use for-in.
- If you need to return early from the surrounding function, use for-in.
- If you just want to perform one short action for every element, forEach is fine.
- If the loop body is growing in complexity, switch to for-in.
- If you are teaching, learning, or prioritizing readability, start with for-in.
9. Key Points
- for-in is a language loop, while forEach is a method that takes a closure.
- for-in supports break, continue, and straightforward early returns.
- forEach is best for short, simple actions applied to every element.
- return inside forEach does not behave like break.
- For most real-world loops, for-in is the clearer default choice.
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.