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-infor the conventional iteration. - Use
wherefor filtered iteration. - Use array methods (
map,filter,reduce) for transformations. - Use
enumerated()for index access. - Use
zip(_:_:)for parallel iteration. - Use
lazyfor substantial chains. - Use
stride(from:to:by:)for stepped iteration. - Use
forEachsparingly —for-inadmits early break. - Use
compactMapfor “filter and map and remove nil”. - Use
flatMapfor nested-flatten or array-of-array patterns. - Use the standard library’s substantial methods —
Sequence/Collectionadmit 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.