How to iterate sequence in Swift

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.

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

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

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

swift
// 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:

swift
// 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:

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

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

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

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

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

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

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

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

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

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

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

swift
let users = ["Alice", "Bob", "Carol"]
users.forEach { name in
    print("Hello, \(name)")
}

Getting Both Index and Value

Use enumerated() when you need both:

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

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

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

Written by

I’m open source contributor, writer, speaker and product maker.

Start the conversation