Polyglot
Languages Swift loops
Swift § loops

Loops

Swift admits four principal iteration forms: for-in (the conventional iterator-based form), while (loop while condition is true), repeat-while (Swift’s “do-while”), and the rare direct iterator manipulation. The conventional Swift discipline favours for-in over explicit indexing — admits substantial conciseness and avoids off-by-one errors. The collection methods (map, filter, reduce, forEach, etc., from Sequence and Collection) admit substantial functional-style transformations. The combination — for-in as the principal form, the where clause for filtered iteration, the substantial collection methods, the lazy sequences for substantial chains — is the substance of Swift’s iteration surface.

for-in

The principal iteration form:

for item in collection {
    use(item)
}

Examples:

let arr = [1, 2, 3, 4, 5]
for x in arr {
    print(x)
}

// Range:
for i in 0..<10 {
    print(i)                                       // 0, 1, ..., 9
}

for i in 1...10 {
    print(i)                                       // 1, 2, ..., 10
}

// String:
for c in "hello" {
    print(c)                                       // Character each
}

// Dictionary:
let scores = ["Alice": 95, "Bob": 87]
for (name, score) in scores {
    print("\(name): \(score)")
}

// Enumerated:
for (i, x) in arr.enumerated() {
    print("[\(i)] = \(x)")
}

// Reversed:
for x in arr.reversed() {
    print(x)
}

// Stride (step):
for i in stride(from: 0, to: 100, by: 5) {
    print(i)                                       // 0, 5, 10, ..., 95
}

for i in stride(from: 0, through: 100, by: 5) {
    print(i)                                       // 0, 5, ..., 100
}

The for-in admits any Sequence; treated in Standard library.

where clause

The for admits a where clause for filtered iteration:

for n in 1...100 where n.isMultiple(of: 7) {
    print(n)                                       // 7, 14, 21, ...
}

for user in users where user.isActive {
    process(user)
}

The form admits substantial filtering inline.

_ for ignoring values

for _ in 1...3 {
    print("hello")                                 // 3 times
}

for _ in 0..<arr.count {
    /* iteration without index */
}

The _ admits “iterate; ignore the value”.

while

while condition {
    body
}

Examples:

var n = 10
while n > 0 {
    print(n)
    n -= 1
}

while !done {
    advance()
}

The conventional uses are condition-driven loops; for known counts, for-in over a range is conventionally clearer.

repeat-while

Swift’s “do-while”:

repeat {
    body
} while condition

The body runs at least once; the condition is checked at the end:

var input: String
repeat {
    input = prompt()
} while !input.isValid

The form is rare in idiomatic Swift; the while-with-break form is conventional.

break and continue

for x in collection {
    if shouldStop(x) { break }                     // exit loop
    if shouldSkip(x) { continue }                  // next iteration
    process(x)
}

For labelled break/continue, label the loop:

outer: for i in 0..<10 {
    for j in 0..<10 {
        if i * j > 50 { break outer }              // breaks the outer loop
    }
}

inner: while !done {
    for item in items where item.shouldHandle {
        if item.terminal { break inner }
        process(item)
    }
}

The labelled form is rare in idiomatic Swift — restructuring into a function with return is conventionally clearer.

Iterator-based iteration

Under the hood, for-in uses Sequence.makeIterator():

let arr = [1, 2, 3]
var iter = arr.makeIterator()
while let next = iter.next() {
    print(next)                                    // 1, 2, 3
}

The form admits substantial flexibility but is rarely used directly.

Collection methods

The conventional Swift discipline favours functional methods over explicit loops for transformations:

forEach

arr.forEach { print($0) }
arr.forEach { x in process(x) }

The forEach is not a transformation — it returns Void. For genuine iteration with side effects.

A subtle limitation: forEach does not admit early termination via break or return:

[1, 2, 3].forEach { x in
    if x > 1 { return }                            // returns from this closure call only
    print(x)
}
// prints 1
// (closure called for 2 and 3, but the return short-circuits each)

// For genuine break, use for-in:
for x in [1, 2, 3] {
    if x > 1 { break }
    print(x)
}

map

let doubled = arr.map { $0 * 2 }
let strings = nums.map(String.init)                // [String]
let lengths = words.map(\.count)                   // key path syntax

filter

let evens = arr.filter { $0.isMultiple(of: 2) }
let positives = nums.filter { $0 > 0 }

reduce

let sum = arr.reduce(0, +)
let max = arr.reduce(Int.min) { max($0, $1) }
let combined = arr.reduce("") { "\($0), \($1)" }

// Reduce into:
var result: [String: Int] = [:]
words.reduce(into: result) { acc, word in
    acc[word, default: 0] += 1
}

The reduce(into:) admits in-place mutation — substantial efficiency.

compactMap

Map and filter out nil:

let strs = ["1", "two", "3", "four", "5"]
let nums = strs.compactMap { Int($0) }            // [1, 3, 5]

flatMap

Flatten nested arrays:

let nested = [[1, 2], [3, 4], [5]]
let flat = nested.flatMap { $0 }                   // [1, 2, 3, 4, 5]

// flatMap on a single level — equivalent to map then flatten:
[1, 2].flatMap { [$0, $0 * 2] }                    // [1, 2, 2, 4]

Other principal methods

arr.first                                          // first or nil
arr.last
arr.first(where: { $0 > 0 })                       // first matching
arr.contains(5)
arr.contains(where: { $0 > 100 })
arr.allSatisfy { $0 > 0 }
arr.firstIndex(of: 5)
arr.firstIndex(where: { $0 > 50 })

arr.sorted()                                       // ascending (Comparable)
arr.sorted(by: >)                                  // descending
arr.sorted { $0.age < $1.age }                     // custom ordering

arr.count
arr.isEmpty
arr.min()
arr.max()
arr.min(by: { $0.age < $1.age })

arr.prefix(5)                                      // first 5
arr.suffix(5)                                      // last 5
arr.dropFirst(3)
arr.dropLast(2)

arr.split(separator: 0)                            // split on value

arr.reversed()
arr.shuffled()                                     // random order

(0..<10).map { $0 * 2 }                            // ranges admit map etc.

lazy sequences

For substantial chains of transformations, lazy admits “compute on demand”:

let result = (1...1_000_000).lazy
    .filter { $0.isMultiple(of: 7) }
    .map { $0 * 2 }
    .prefix(10)
    .reduce(0, +)

// Without lazy, each intermediate creates a full collection;
// with lazy, items flow through the pipeline one at a time.

The mechanism admits substantial efficiency for substantial pipelines.

Common patterns

Iterate with index

for (i, item) in arr.enumerated() {
    print("[\(i)] \(item)")
}

Index-based iteration

for i in arr.indices {
    print(arr[i])
}

// Modify in place:
for i in arr.indices {
    arr[i] *= 2
}

Range iteration

for n in 0..<10 { /* exclusive */ }
for n in 0...10 { /* inclusive */ }
for n in stride(from: 0, to: 10, by: 2) { /* step */ }
for n in (0..<10).reversed() { /* reverse */ }

Sum, max, min

let sum = arr.reduce(0, +)
let max = arr.max() ?? 0
let min = arr.min() ?? 0
let count = arr.count
let avg = arr.reduce(0, +) / arr.count             // for non-empty Int arrays

Group by

let grouped = Dictionary(grouping: people) { $0.age / 10 * 10 }
// [decade: [people]]

Tally

var counts: [String: Int] = [:]
for word in words {
    counts[word, default: 0] += 1
}

// Or with reduce:
let counts2 = words.reduce(into: [String: Int]()) { acc, word in
    acc[word, default: 0] += 1
}

Find first matching

let user = users.first { $0.id == target }
let idx = arr.firstIndex(where: { $0 > 0 })

Filter and map combined

let result = items
    .filter { $0.isActive }
    .map { $0.name.uppercased() }
    .sorted()

// Or with compactMap (filter + map + remove nil):
let names = users.compactMap { $0.profile?.name }

Iterate dictionary in sorted order

let dict = ["c": 1, "a": 2, "b": 3]

for (key, value) in dict.sorted(by: { $0.key < $1.key }) {
    print("\(key): \(value)")
}
// a: 2
// b: 3
// c: 1

Pairs of consecutive elements

let arr = [1, 2, 3, 4, 5]
for (a, b) in zip(arr, arr.dropFirst()) {
    print("\(a)\(b)")                           // 1→2, 2→3, 3→4, 4→5
}

Sliding window

extension Array {
    func slidingWindows(ofSize size: Int) -> [ArraySlice<Element>] {
        guard count >= size else { return [] }
        return (0...(count - size)).map { self[$0..<$0+size] }
    }
}

[1, 2, 3, 4, 5].slidingWindows(ofSize: 3)
// [[1,2,3], [2,3,4], [3,4,5]]

Iterating async sequences

For async sequences (Swift 5.5+):

for try await line in fileHandle.bytes.lines {
    process(line)
}

for await event in events {
    handle(event)
}

Treated in Concurrency.

Bounded iteration with break

for x in collection {
    process(x)
    if shouldStop { break }
}

Skipping with continue

for x in items {
    guard x.isValid else { continue }
    process(x)
}

// Or with where:
for x in items where x.isValid {
    process(x)
}

Converting iteration to array

let arr = Array(0..<10)                            // [0, 1, ..., 9]
let chars = Array("hello")                         // [Character]
let lines = Array(file.bytes.lines)                // (async; for AsyncSequence)

Generating a sequence

let powers = sequence(first: 1) { $0 * 2 }.prefix(10)
// [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

let collatz = sequence(first: 27) { n -> Int? in
    if n == 1 { return nil }
    return n.isMultiple(of: 2) ? n / 2 : n * 3 + 1
}

print(Array(collatz))                              // Collatz sequence from 27

The sequence(first:next:) admits substantial generator-like patterns.

Custom iterators

struct CountDown: Sequence, IteratorProtocol {
    var count: Int

    mutating func next() -> Int? {
        if count == 0 { return nil }
        defer { count -= 1 }
        return count
    }
}

for n in CountDown(count: 5) {
    print(n)                                       // 5, 4, 3, 2, 1
}

The pattern admits custom iteration sources.

A note on the conventional discipline

The contemporary Swift loops advice:

  • Use for-in for the conventional iteration.
  • Use where for filtered iteration.
  • Use array methods (map, filter, reduce) for transformations.
  • Use enumerated() for index access.
  • Use zip(_:_:) for parallel iteration.
  • Use lazy for substantial chains.
  • Use stride(from:to:by:) for stepped iteration.
  • Use forEach sparingly — for-in admits early break.
  • Use compactMap for “filter and map and remove nil”.
  • Use flatMap for nested-flatten or array-of-array patterns.
  • Use the standard library’s substantial methodsSequence/Collection admit substantial functional patterns.

The combination — for-in as the principal form, the where clause for filtering, the substantial collection methods, lazy sequences for substantial chains, the async iteration forms (5.5+) — is the substance of Swift’s iteration surface. The discipline produces concise, expressive iteration code with substantial functional-style flexibility.