Issue #1014
You need to loop through arrays, make sequences, change data, and repeat tasks. Swift has many ways to do this beyond the basic for loop. Knowing your options helps you write clearer code.
The Standard For Loop
The for-in loop handles the common case. When you have a collection and need to visit each item, use this.
let temperatures = [72, 68, 75, 71]
for temp in temperatures {
print("\(temp)°F")
}
This works with any type that conforms to Sequence. Arrays, sets, dictionaries, and ranges all work. Swift handles the details for you.
For dictionaries, you get both key and value:
let scores = ["Alice": 95, "Bob": 87, "Carol": 92]
for (name, score) in scores {
print("\(name): \(score)")
}
Ranges let you loop through numbers without making an array:
for index in 0..<5 {
print("Item \(index)")
}
Use ..< to exclude the end value, or ... to include it.
Stride for Custom Steps
When you need control over the step size, use stride:
// Count by twos from 0 to 10 (excluding 10)
for value in stride(from: 0, to: 10, by: 2) {
print(value) // 0, 2, 4, 6, 8
}
The to version stops before the end. Use through to include it:
// Count by fives from 0 to 20 (including 20)
for value in stride(from: 0, through: 20, by: 5) {
print(value) // 0, 5, 10, 15, 20
}
Negative steps work for counting down:
for countdown in stride(from: 10, through: 0, by: -1) {
print(countdown)
}
Building Custom Sequences
The sequence(first:next:) function creates values on demand. You give it a starting value and a rule for the next one.
Powers of two show the pattern:
for power in sequence(first: 1, next: { $0 * 2 }) {
if power > 1000 { break }
print(power) // 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
}
Each value gets doubled. The loop goes forever, so you need a break condition.
For Fibonacci numbers, track two values:
for fib in sequence(state: (0, 1), next: { state in
let next = state.0 + state.1
state = (state.1, next)
return state.0
}) {
if fib > 100 { break }
print(fib) // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
}
The state version lets you keep multiple values between steps.
The Iterator Protocol
Sequences work by making iterators. An iterator has a next() method that returns the next value or nil when done:
struct CountdownIterator: IteratorProtocol {
var current: Int
mutating func next() -> Int? {
guard current >= 0 else { return nil }
defer { current -= 1 }
return current
}
}
struct Countdown: Sequence {
let start: Int
func makeIterator() -> CountdownIterator {
CountdownIterator(current: start)
}
}
for number in Countdown(start: 5) {
print(number) // 5, 4, 3, 2, 1, 0
}
The iterator keeps track of state. Each call to next() returns a value and updates the state.
While and Repeat-While Loops
Use while when you don’t know how many times to loop:
var temperature = 212.0
while temperature > 70 {
temperature -= 5
print("Cooling: \(temperature)°F")
}
The condition checks before each loop, including the first one.
Use repeat-while when you need at least one loop:
var input: String
repeat {
print("Enter 'quit' to exit:")
input = readLine() ?? ""
} while input != "quit"
The condition checks after each loop, not before.
Higher-Order Functions
When changing or filtering collections, these functions often read better than loops.
The map function changes each item:
let prices = [29.99, 15.50, 8.75]
let rounded = prices.map { Int($0) }
print(rounded) // [29, 15, 8]
Filter keeps only items that match a rule:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evens = numbers.filter { $0 % 2 == 0 }
print(evens) // [2, 4, 6, 8, 10]
Chain them together:
let words = ["Swift", "is", "a", "powerful", "language"]
let longWords = words
.filter { $0.count > 3 }
.map { $0.uppercased() }
print(longWords) // ["SWIFT", "POWERFUL", "LANGUAGE"]
The reduce function combines all items into one value:
let expenses = [12.50, 8.25, 15.00, 6.75]
let total = expenses.reduce(0, +)
print(total) // 42.5
You can write custom logic too:
let items = ["apple", "banana", "cherry"]
let result = items.reduce("") { result, item in
result + (result.isEmpty ? "" : ", ") + item
}
print(result) // "apple, banana, cherry"
The forEach function runs code for each item but returns nothing:
let users = ["Alice", "Bob", "Carol"]
users.forEach { name in
print("Hello, \(name)")
}
Getting Both Index and Value
Use enumerated() when you need both:
let fruits = ["apple", "banana", "cherry"]
for (index, fruit) in fruits.enumerated() {
print("\(index): \(fruit)")
}
// 0: apple
// 1: banana
// 2: cherry
Zipping Multiple Sequences
Use zip to loop through two sequences at once:
let names = ["Alice", "Bob", "Carol"]
let ages = [28, 34, 25]
for (name, age) in zip(names, ages) {
print("\(name) is \(age) years old")
}
The loop stops when either sequence ends.
Practical Tips
Always add a stop condition for infinite sequences. Both sequence(first:next:) and custom iterators can go forever.
For large datasets, use lazy to avoid making extra arrays:
let numbers = 1...1_000_000
let result = numbers
.lazy
.filter { $0 % 2 == 0 }
.map { $0 * 2 }
.prefix(10)
This processes items one at a time instead of making temporary arrays.
Pick the style that makes your intent clear. Use for-in for simple loops. Use higher-order functions for changing or filtering collections. Make custom sequences when you have a pattern to generate. Match the tool to the job.
Start the conversation