Swift forEach vs for-in: Differences, Use Cases, and Errors
Swift gives you two common ways to iterate over collections: the for-in loop and the forEach method. They can look similar in simple examples, but they behave differently in important ways, especially around control flow, readability, and how you structure your code. This article explains exactly how each one works, when to use each option, and the mistakes that often confuse beginners.
Quick answer: Use for-in when you need full loop control, especially break, continue, or early exit from the loop body. Use forEach when you want to apply the same action to every element and do not need traditional loop control.
Difficulty: Beginner
Helpful to know first: You will understand this better if you already know basic Swift syntax, arrays and dictionaries, and how closures work at a simple level.
1. What Are the Options?
This comparison matters because both for-in and forEach let you go through items in a collection, but they are not interchangeable in every situation.
- for-in is a language loop statement built into Swift.
- forEach is a method available on sequences and collections that takes a closure.
- for-in supports normal loop control like break and continue.
- forEach always visits every element unless the program throws an error or exits the surrounding scope for another reason.
- for-in often reads more clearly for branching logic.
- forEach can feel concise when you only want to perform one action for each value.
In other words, for-in is a loop construct, while forEach is a function-style operation.
2. Side-by-Side Comparison
| Feature | for-in | forEach |
|---|---|---|
| Type | Language loop syntax | Method on sequences/collections |
| Basic style | Imperative loop | Closure-based iteration |
| Can use break | Yes | No |
| Can use continue | Yes | No |
| Can use return | Returns from surrounding function if written there | return exits only the current closure call |
| Readability for simple actions | Good | Often concise |
| Readability for complex conditions | Usually better | Often worse |
| Best use case | General-purpose iteration | Apply one action to all elements |
3. Key Differences Explained
Loop syntax vs method call
for-in is part of the Swift language. It is designed specifically for iteration and integrates naturally with control-flow keywords.
forEach is a method that accepts a closure. That means the body you write is closure code, not a traditional loop body. This difference explains many of the surprising behaviors people run into.
Control flow behavior
The biggest practical difference is control flow.
- With for-in, you can skip an item with continue.
- With for-in, you can stop early with break.
- With forEach, neither break nor continue works.
- Inside forEach, a return only exits that one closure execution, not the surrounding loop in the same way developers often expect.
Readability
If your code simply prints values, logs them, or applies one short operation to each element, forEach can be clean and expressive.
If your code needs branching, skipping, searching, nested conditions, or early termination, for-in is usually easier to read and maintain.
Intent
forEach communicates, “run this action for each element.”
for-in communicates, “I am looping, and I may use loop logic.”
That makes for-in the safer default when you are not sure which one to choose.
4. When to Use Each
Here are practical rules you can rely on.
Use for-in when:
- You need break to stop early.
- You need continue to skip some values.
- You are searching for a match and want to stop once you find it.
- You have multiple conditions or more complex control flow.
- You want the clearest possible loop for most readers.
Use forEach when:
- You want to perform one straightforward action on every element.
- You do not need to stop or skip with loop keywords.
- You want a concise closure-based style.
- You are already working in a chain of sequence operations and the style fits naturally.
A good rule for beginners is simple: start with for-in. Switch to forEach only when it clearly improves readability.
5. Code Examples
Example 1: Printing every value
Both approaches work well when you want to process every element with the same simple action.
let scores = [85, 90, 78]Using for-in:
for score in scores {
print(score)
}Using forEach:
scores.forEach { score in
print(score)
}These examples produce the same output. In a simple case like this, choosing between them is mostly a style decision.
Example 2: Skipping values
This is where the difference becomes important. Suppose you want to print only even numbers.
With for-in, continue makes the intent obvious.
let numbers = [1, 2, 3, 4, 5]for number in numbers {
if number % 2 != 0 {
continue
}
print(number)
}With forEach, you cannot use continue, but you can simulate a skip by returning from the closure early.
numbers.forEach { number in
if number % 2 != 0 {
return
}
print(number)
}This works, but many developers find the for-in version easier to understand because continue clearly says “skip this iteration.”
Example 3: Stopping early when a match is found
If you want to stop after finding the first value greater than 100, for-in is the better tool.
let values = [40, 75, 130, 160]for value in values {
if value > 100 {
print("Found: \(value)")
break
}
}Trying to do the same thing with forEach does not provide a true early exit.
values.forEach { value in
if value > 100 {
print("Found: \(value)")
return
}
}This closure returns only for the current element. The method still continues with the remaining elements. That is the most important behavioral difference in everyday Swift code.
Example 4: Iterating over a dictionary
Both approaches also work with dictionaries.
let stock: [String: Int] = [
"Apples": 10,
"Bananas": 5
]Using for-in:
for (item, count) in stock {
print("\(item): \(count)")
}Using forEach:
stock.forEach { entry in
print("\(entry.key): \(entry.value)")
}The for-in version often reads more naturally because tuple destructuring happens directly in the loop header.
6. Common Mistakes
Mistake 1: Expecting break to work inside forEach
A very common beginner mistake is treating forEach like a normal loop and trying to stop it with break.
Problem: break is only valid in loop or switch contexts. A forEach body is a closure, so Swift reports an error because there is no loop statement to break out of.
let numbers = [1, 2, 3, 4]
numbers.forEach { number in
if number == 3 {
break
}
print(number)
}Fix: Use for-in when you need to stop early.
for number in [1, 2, 3, 4] {
if number == 3 {
break
}
print(number)
}The corrected version works because for-in is a real loop that supports break.
Mistake 2: Expecting continue to work inside forEach
Another common assumption is that continue can skip the current forEach iteration.
Problem: continue only works in loop statements. Inside a closure passed to forEach, Swift cannot apply loop control in the usual way.
numbers.forEach { number in
if number % 2 != 0 {
continue
}
print(number)
}Fix: Either switch to for-in or use return to exit the current closure call early.
numbers.forEach { number in
if number % 2 != 0 {
return
}
print(number)
}The corrected version works because return exits just that closure execution, effectively skipping the rest of the code for the current element.
Mistake 3: Thinking return exits the entire iteration process
Developers sometimes expect return inside forEach to behave like a loop-wide early exit.
Problem: In a forEach closure, return exits only the closure call for the current item. The remaining elements are still processed, which can lead to unexpected extra output or logic running longer than intended.
let values = [10, 20, 30]
values.forEach { value in
if value == 20 {
return
}
print("Processing \(value)")
}Fix: Use for-in with break if you need true early termination.
for value in [10, 20, 30] {
if value == 20 {
break
}
print("Processing \(value)")
}The corrected version works because break stops the loop completely as soon as the condition is met.
7. Tradeoffs and Edge Cases
- Not all iteration problems should use either one. If you need to transform values into a new array, map is often better. If you need filtering, filter may express the intent more clearly.
- forEach is not automatically more functional or more efficient. It is just a different API style. Choose based on readability and behavior, not on the assumption that it is inherently better.
- Nested closures can become confusing. A return inside a nested forEach may exit only the innermost closure, which can make the code hard to reason about.
- Dictionary order should not be relied on unless order is explicitly managed. This matters whether you use for-in or forEach.
- Complex logic usually favors for-in. Even when forEach can be made to work, closure-heavy code may be harder for teams to maintain.
8. Decision Guide
If you want a fast practical rule, use this:
- If you need break, use for-in.
- If you need continue, use for-in.
- If you are applying one short action to every element, forEach is fine.
- If your loop has branching, searching, or multiple conditions, prefer for-in.
- If you are unsure, choose for-in first because it is more flexible and usually easier to understand.
A useful mental model is this: forEach is for “do this to every item,” while for-in is for “run loop logic over items.”
9. Key Points
- for-in is a loop statement; forEach is a method that takes a closure.
- Use for-in when you need break, continue, or clearer control flow.
- Use forEach for simple, uniform actions on every element.
- return inside forEach skips only the current closure execution, not the entire iteration.
- for-in is usually the better default choice for beginners.
10. Final Summary
forEach and for-in both help you iterate through Swift collections, but they serve different styles of code. for-in is a full loop construct with direct support for control flow, which makes it the better option for most real-world looping tasks. forEach is best when your intent is simply to perform the same action for every element and you do not need to stop or skip in the traditional loop sense.
The most important distinction to remember is that forEach uses a closure, so keywords like break and continue do not behave as they do in a loop. If you keep that one rule in mind, choosing between them becomes much easier. A good next step is to compare iteration tools such as map, filter, and compactMap so you can choose the right approach for each collection task.