Skip to content

Control Flow

Swift provides control flow statements to structure code execution, including loops, conditional branches, and early exits. These tools allow you to iterate over sequences, make decisions based on conditions, and control the flow of execution. Key control flow mechanisms include for-in loops, while loops, if, guard, switch, defer, and control transfer statements like break, continue, and fallthrough.

Note

Swift’s control flow is type-safe and optimized for clarity and performance. Always ensure conditions and patterns are exhaustive to avoid runtime errors.

For-In Loops

The for-in loop iterates over sequences like arrays, dictionaries, ranges, or strings. It’s ideal for processing collections or performing tasks a set number of times.

Iterating Over an Array

swift
let fruits = ["Apple", "Banana", "Orange"]
for fruit in fruits {
    print("I like \(fruit)!")
}
// Prints:
// I like Apple!
// I like Banana!
// I like Orange!

Iterating Over a Dictionary

Dictionaries return key-value pairs as tuples during iteration. You can decompose these tuples into named constants:

swift
let pets = ["Dog": 4, "Bird": 2, "Fish": 0]
for (pet, legs) in pets {
    print("\(pet)s have \(legs) legs.")
}
// Prints (order not guaranteed):
// Dogs have 4 legs.
// Birds have 2 legs.
// Fish have 0 legs.

Note

Dictionary iteration order is not guaranteed. To iterate in a specific order, use sorted() on keys or values. For example:

swift
for pet in pets.keys.sorted() {
    print("\(pet): \(pets[pet]!) legs")
}
// Prints in alphabetical order:
// Bird: 2 legs
// Dog: 4 legs
// Fish: 0 legs

Iterating Over a Range

Use ranges for numeric iteration:

swift
for num in 1...3 {
    print("\(num) squared is \(num * num)")
}
// Prints:
// 1 squared is 1
// 2 squared is 4
// 3 squared is 9

Ignoring Loop Values

Use an underscore (_) to ignore loop values when they’re not needed:

swift
var total = 0
for _ in 1...5 {
    total += 10
}
print("Total is \(total)")
// Prints: Total is 50

Half-Open Ranges and Strides

Use ..< for half-open ranges (excluding the upper bound):

swift
for tick in 0..<4 {
    print("Tick \(tick)")
}
// Prints:
// Tick 0
// Tick 1
// Tick 2
// Tick 3

Use stride(from:to:by:) for custom increments, excluding the upper bound:

swift
for tick in stride(from: 0, to: 10, by: 2) {
    print("Tick \(tick)")
}
// Prints:
// Tick 0
// Tick 2
// Tick 4
// Tick 6
// Tick 8

Use stride(from:through:by:) to include the upper bound:

swift
for tick in stride(from: 2, through: 8, by: 2) {
    print("Tick \(tick)")
}
// Prints:
// Tick 2
// Tick 4
// Tick 6
// Tick 8

Note

Any type conforming to the Sequence protocol can be used in a for-in loop. Custom collections can implement this protocol for iteration.

While Loops

While loops execute statements until a condition becomes false. They’re useful when the number of iterations is unknown. Swift offers two forms:

  • while: Evaluates the condition before each iteration.
  • repeat-while: Executes at least once, evaluating the condition after each iteration.

While Loop

swift
var count = 0
while count < 3 {
    print("Count: \(count)")
    count += 1
}
// Prints:
// Count: 0
// Count: 1
// Count: 2

Repeat-While Loop

swift
var steps = 0
repeat {
    print("Step: \(steps)")
    steps += 1
} while steps < 3
// Prints:
// Step: 0
// Step: 1
// Step: 2

Example: Number Guessing Game

This example simulates a number guessing game where the player tries to guess a target number:

swift
let target = 7
var guess = 1
while guess != target {
    print("Guessing \(guess)...")
    guess += 1
}
print("Correct! The number is \(guess).")
// Prints:
// Guessing 1...
// Guessing 2...
// Guessing 3...
// Guessing 4...
// Guessing 5...
// Guessing 6...
// Correct! The number is 7.

Using repeat-while:

swift
guess = 1
repeat {
    print("Guessing \(guess)...")
    guess += 1
} while guess != target
print("Correct! The number is \(guess).")
// Same output as above

Note

repeat-while guarantees at least one iteration, making it suitable when initial execution is required before condition checking.

Conditional Statements

Swift provides if and switch statements for conditional execution. If is suited for simple conditions, while switch handles complex pattern matching.

If Statement

An if statement executes code based on a Boolean condition:

swift
let score = 85
if score >= 90 {
    print("Excellent!")
} else if score >= 70 {
    print("Good job!")
} else {
    print("Try again!")
}
// Prints: Good job!

The else clause is optional:

swift
if score >= 90 {
    print("Excellent!")
}
// No output if score < 90

If Expression

Swift supports if expressions for concise value assignments:

swift
let grade = if score >= 90 {
    "A"
} else if score >= 70 {
    "B"
} else {
    "C"
}
print("Grade: \(grade)")
// Prints: Grade: B

For optional values, specify the type explicitly:

swift
let bonus: String? = if score >= 90 {
    "Extra credit earned!"
} else {
    nil as String?
}
print(bonus ?? "No bonus")
// Prints: No bonus

Note

All branches in an if expression must return the same type to ensure type safety.

Switch Statement

A switch statement matches a value against patterns and executes the first matching case. It must be exhaustive, often using a default case.

swift
let day = "Monday"
switch day {
case "Monday":
    print("Start of the week")
case "Friday":
    print("Weekend’s here!")
default:
    print("Just another day")
}
// Prints: Start of the week

Switch Expression

Like if, switch can be used as an expression:

swift
let message = switch day {
case "Monday":
    "Start of the week"
case "Friday":
    "Weekend’s here!"
default:
    "Just another day"
}
print(message)
// Prints: Start of the week

No Implicit Fallthrough

Swift switch statements don’t fall through to the next case, preventing unintended execution:

swift
let letter = "a"
switch letter {
case "a", "e":
    print("Vowel")
case "b", "c":
    print("Consonant")
default:
    print("Other")
}
// Prints: Vowel

Note

Each case must have at least one executable statement. Empty cases cause compile-time errors unless using break.

Interval Matching

Match ranges of values:

swift
let distance = 25
let rangeDescription = switch distance {
case 0:
    "At the origin"
case 1..<10:
    "Nearby"
case 10..<50:
    "Moderate distance"
default:
    "Far away"
}
print(rangeDescription)
// Prints: Moderate distance

Tuple Matching

Match multiple values using tuples:

swift
let point = (3, 0)
switch point {
case (0, 0):
    print("At origin")
case (_, 0):
    print("On x-axis")
case (0, _):
    print("On y-axis")
case (1...5, 1...5):
    print("In 5x5 square")
default:
    print("Elsewhere")
}
// Prints: On x-axis

Value Bindings

Bind values to constants within a case:

swift
switch point {
case (let x, 0):
    print("X-axis at \(x)")
case (0, let y):
    print("Y-axis at \(y)")
default:
    print("Elsewhere")
}
// Prints: X-axis at 3

Where Clause

Add conditions to cases with where:

swift
let coords = (2, 2)
switch coords {
case let (x, y) where x == y:
    print("On line x = y")
case let (x, y) where x == -y:
    print("On line x = -y")
default:
    print("Elsewhere")
}
// Prints: On line x = y

Compound Cases

Combine multiple patterns in a single case:

swift
let char = "e"
switch char {
case "a", "e", "i":
    print("Vowel")
case "b", "c", "d":
    print("Consonant")
default:
    print("Other")
}
// Prints: Vowel

Compound cases with value bindings require consistent types:

swift
let position = (5, 0)
switch position {
case (let dist, 0), (0, let dist):
    print("On axis, distance \(dist)")
default:
    print("Not on axis")
}
// Prints: On axis, distance 5

Patterns in If and For Loops

Patterns can be used in if and for-case-in constructs for concise matching.

If-Case

swift
let testPoint = (10, 100)
if case (let x, 100) = testPoint {
    print("Y=100 at x=\(x)")
}
// Prints: Y=100 at x=10

For-Case-In

Filter collections with patterns:

swift
let points = [(1, 0), (2, 2), (0, 3)]
for case (let x, 0) in points {
    print("X-axis point at \(x)")
}
// Prints: X-axis point at 1

Add a where clause for additional filtering:

swift
for case let (x, y) in points where x == y {
    print("Diagonal point at (\(x), \(y))")
}
// Prints: Diagonal point at (2, 2)

Control Transfer Statements

Control transfer statements alter execution flow:

  • continue: Skips to the next loop iteration.
  • break: Exits a loop or switch immediately.
  • fallthrough: Continues to the next switch case.
  • return: Exits a function (see Functions).
  • throw: Propagates errors (see Error Handling).

Continue

Skip the current iteration:

swift
let text = "hello world"
var result = ""
let skip = ["e", "o", " "]
for char in text {
    if skip.contains(char) {
        continue
    }
    result.append(char)
}
print(result)
// Prints: hllwrld

Break in Loops

Exit a loop early:

swift
var sum = 0
for num in 1...10 {
    if sum > 15 {
        break
    }
    sum += num
}
print("Sum: \(sum)")
// Prints: Sum: 21 (stops after 1+2+3+4+5+6)

Break in Switch

Ignore a case with break:

swift
let symbol: Character = "二"
var number: Int?
switch symbol {
case "1", "一":
    number = 1
case "2", "二":
    number = 2
default:
    break
}
print(number ?? "No number")
// Prints: 2

Fallthrough

Opt into C-style fallthrough behavior:

swift
let value = 7
var desc = "Number \(value) is"
switch value {
case 3, 5, 7:
    desc += " a small prime"
    fallthrough
default:
    desc += " and an integer"
}
print(desc)
// Prints: Number 7 is a small prime and an integer

Labeled Statements

Labels clarify which loop or switch a break or continue targets in nested structures:

swift
outerLoop: for i in 1...3 {
    for j in 1...3 {
        if j == 2 {
            continue outerLoop
        }
        print("i=\(i), j=\(j)")
    }
}
// Prints:
// i=1, j=1
// i=2, j=1
// i=3, j=1

Example: Maze Navigation

Simulate navigating a maze where the player must reach position 10 exactly:

swift
let target = 10
var position = 0
var move = 0
mazeLoop: while position != target {
    move += 1
    if move == 6 { move = 1 }
    switch position + move {
    case target:
        position = target
        break mazeLoop
    case let newPos where newPos > target:
        continue mazeLoop
    default:
        position += move
    }
}
print("Reached position \(position)!")
// Prints: Reached position 10!

Early Exit with Guard

A guard statement ensures a condition is true to continue execution, otherwise it executes an else block that must exit the scope:

swift
func processUser(data: [String: String]) {
    guard let username = data["username"] else {
        print("No username provided")
        return
    }
    print("Welcome, \(username)!")
}
processUser(data: ["username": "Alice"])
// Prints: Welcome, Alice!
processUser(data: [:])
// Prints: No username provided

Note

guard improves readability by placing error-handling code near the condition and avoiding nested if blocks.

Deferred Actions

Use defer to execute code just before leaving a scope, regardless of how the scope is exited:

swift
var balance = 100
if balance < 1000 {
    defer { print("Final balance: \(balance)") }
    balance += 50
}
print("Processing complete")
// Prints:
// Final balance: 150
// Processing complete

Multiple defer blocks execute in reverse order:

swift
if balance < 1000 {
    defer { print("Balance: \(balance)") }
    defer { print("Checking balance...") }
    balance += 100
}
// Prints:
// Checking balance...
// Balance: 200

Note

Deferred code runs after errors are thrown but not after crashes or runtime errors.

Checking API Availability

Use #available to check if APIs are available for a deployment target:

swift
if #available(iOS 15, macOS 12, *) {
    print("Using modern APIs")
} else {
    print("Using legacy APIs")
}

Use guard for availability in a scope:

swift
func useFeature() -> String {
    guard #available(iOS 15, *) else {
        return "Feature unavailable"
    }
    return "Using iOS 15 feature"
}

Use #unavailable for fallback code:

swift
if #unavailable(iOS 15) {
    print("Fallback for older iOS")
}

Note

Specify platforms and versions explicitly, using * for others. Minor versions (e.g., iOS 15.2) are supported.

Released under the MIT License.