Control Flow
Control flow decides which code runs, and how often. Swift gives you the familiar loops and conditionals plus a genuinely powerful switch — one with pattern matching, value binding, and no accidental fallthrough. This chapter covers loops, branching, control transfer, labeled statements, early exit, and runtime availability checks.
For-in loops
The for-in loop iterates over a sequence: a range, an array, a string, a dictionary, and more. Use _ when you don't need the current value.
for index in 1...3 {
print(index) // → 1, 2, 3
}
let names = ["Anna", "Alex"]
for name in names {
print("Hello, \(name)!")
}
// Ignore the value when you only need repetition
var total = 1
for _ in 1...4 { total *= 2 }
print(total) // → 16
Iterating a dictionary yields key-value tuples. To step by a fixed amount or skip values, use stride(from:to:by:) (half-open) or stride(from:through:by:) (closed).
let ages = ["Ada": 36, "Grace": 45]
for (name, age) in ages {
print("\(name) is \(age)")
}
for tick in stride(from: 0, to: 60, by: 15) {
print(tick) // → 0, 15, 30, 45
}
While and repeat-while
Use while when you don't know the number of iterations up front; it tests its condition before each pass. Use repeat-while when the body must run at least once — it tests after.
var square = 1
while square < 100 {
square *= 2
}
print(square) // → 128
var n = 5
repeat {
print(n)
n -= 1
} while n > 0
// → 5, 4, 3, 2, 1
If statements
An if runs a block when its condition is true, optionally chained with else if and else. In Swift, if can also be an expression that produces a value — handy for initializing a constant.
let temperature = 28
if temperature <= 0 {
print("Freezing")
} else if temperature >= 30 {
print("Hot")
} else {
print("Comfortable") // → Comfortable
}
// if as an expression (Swift 5.9+)
let label = if temperature >= 30 { "Hot" } else { "OK" }
print(label) // → OK
Switch
A switch compares a value against several matching patterns and runs the first that matches. Swift's switch must be exhaustive — every possible value has to be covered (a default case handles the rest) — and there's no implicit fallthrough: each case ends on its own.
let ch: Character = "z"
switch ch {
case "a":
print("First letter")
case "z":
print("Last letter") // → Last letter
default:
print("Some other letter")
}
Interval matching and compound cases
Cases can match ranges, and a single case can list several patterns separated by commas (a compound case).
let score = 83
switch score {
case 0..<60:
print("Fail")
case 60..<80:
print("Pass")
case 80...100:
print("Distinction") // → Distinction
default:
print("Out of range")
}
let vowel: Character = "e"
switch vowel {
case "a", "e", "i", "o", "u":
print("Vowel") // → Vowel
default:
print("Consonant")
}
Tuples, value bindings, and where
Switch over a tuple to match several values at once, use _ as a wildcard, bind matched values to temporary constants, and add a where clause for extra conditions.
let point = (1, -1)
switch point {
case (0, 0):
print("Origin")
case (let x, 0):
print("On the x-axis at \(x)")
case (let x, let y) where x == y:
print("On the line x == y")
case (let x, let y) where x == -y:
print("On the line x == -y") // → On the line x == -y
case let (x, y):
print("Somewhere at (\(x), \(y))")
}
Control transfer statements
Five statements change which code runs next: continue, break, fallthrough, return, and throw. continue skips to the next loop iteration; break ends a loop or a switch case early. Because switch cases don't fall through automatically, fallthrough explicitly continues into the next case's body.
for number in 1...10 {
if number % 2 == 0 { continue } // skip evens
print(number, terminator: " ") // → 1 3 5 7 9
}
let n = 3
var description = "\(n) is"
switch n {
case 3, 5, 7:
description += " prime, and also"
fallthrough
default:
description += " an integer."
}
print(description) // → 3 is prime, and also an integer.
return exits a function (covered in the Functions chapter), and throw raises an error (covered in Error Handling).
Labeled statements
When loops are nested, a plain break or continue affects only the innermost loop. Attach a label to a loop and name it in the transfer statement to act on the outer loop instead.
search: for row in 0..<3 {
for col in 0..<3 {
if row == 1 && col == 2 {
print("Found at (1, 2)")
break search // exits the outer loop
}
}
}
// → Found at (1, 2)
Early exit with guard
A guard statement runs its else block and exits the current scope when its condition is false. Its great strength is keeping the happy path un-indented: any value it binds stays available for the rest of the scope.
func greet(_ person: [String: String]) {
guard let name = person["name"] else {
print("No name provided.")
return
}
guard let city = person["city"] else {
print("Hello, \(name)!")
return
}
print("Hello, \(name) from \(city)!")
}
greet(["name": "Jane", "city": "Cupertino"])
// → Hello, Jane from Cupertino!
Tip: The else branch of a guard must leave the scope — with return, break, continue, throw, or a call that never returns. The compiler enforces it, which is what makes guard reliable for validation.
Checking API availability
To use APIs that only exist on newer OS versions while still running on older ones, gate them with an availability condition. The #available check tests the runtime version; #unavailable is its inverse.
if #available(iOS 18, macOS 15, *) {
// Use APIs introduced in iOS 18 / macOS 15
print("New API available")
} else {
// Fall back for earlier versions
print("Using the fallback path")
}
guard #available(iOS 18, *) else { return }
// New API safe to use from here on
Note: The trailing * is required. It means "and every other platform" — so the check still compiles and behaves sensibly on platforms you didn't list explicitly.
Wrapping up
You've covered Swift's full control-flow toolkit: for-in and stride, the two while forms, if as a statement and expression, the pattern-matching power of switch, the control-transfer statements, labeled loops, early exit with guard, and runtime availability checks. With the fundamentals complete, you're ready to package logic into reusable units — functions, up next.