Structuring Code with Branches, Loops, and Early Exits
  • July 23, 2025

1. Structuring Code with Branches, Loops, and Early Exits

Control flow is about choosing paths (branches), repeating work (loops), and bailing out early (early exits). Swift emphasizes clarity and safety: booleans must be explicit, conditions can bind values, and many features (like switch patterns or guard) help you express intent succinctly.


2. For-In Loops

Use for-in to iterate over sequences (arrays, ranges, dictionaries, sets, strings, async sequences).

let scores = [10, 15, 8]
for score in scores {
    print(score)
}

for i in 0..<scores.count {       // half-open range
    print("Index \(i): \(scores[i])")
}

let capitals = ["UK": "London", "FR": "Paris"]
for (country, city) in capitals {
    print("\(country): \(city)")
}

Key points:

  • Works with any type conforming to Sequence.
  • for _ in ignores the loop variable.
  • Prefer for-in over C-style for (which Swift removed).
  • In async contexts, for try await lets you iterate over an AsyncSequence.

3. While Loops

Swift offers while and repeat { ... } while. Use them when you don’t know the exact iteration count beforehand.

var x = 3
while x > 0 {
    x -= 1
}

var input: String
repeat {
    input = readLine() ?? ""
} while input.isEmpty
  • while checks the condition before each iteration.
  • repeat-while runs the body once, then checks.
  • Avoid infinite loops; always move state forward. If you need an intentional infinite loop (e.g., game loop), ensure there’s a break condition inside.

4. Conditional Statements (if, else if, else)

Classic branching:

let temperature = 28
if temperature > 30 {
    print("Hot")
} else if temperature > 15 {
    print("Mild")
} else {
    print("Cold")
}

Swift requires a Boolean in if—no implicit integer-to-bool conversions. You can also bind optionals:

if let first = scores.first, first > 10 {
    print("First score is big")
}

Or use guard for early exits (see §7).

Ternary operator (condition ? a : b) is available but keep it readable.


5. switch

switch in Swift is powerful: exhaustive by default, supports complex patterns, value binding, where clauses, and doesn’t fall through implicitly.

let point = (2, 0)

switch point {
case (0, 0):
    print("Origin")
case (let x, 0):
    print("On x-axis at \(x)")
case (0, let y):
    print("On y-axis at \(y)")
case (-2...2, -2...2):
    print("Near the origin")
default:
    print("Somewhere else")
}

Notes:

  • Must cover every possible value (use default to catch the rest).
  • No implicit fallthrough; each case is its own scope.
  • Multiple values per case: case "a", "e", "i", "o", "u":.
  • Works with enums (preferred):
enum Direction { case north, south, east, west }

let d: Direction = .north
switch d {
case .north, .south:
    print("Vertical")
case .east, .west:
    print("Horizontal")
}

6. Patterns & Matching Power

Swift’s pattern matching extends beyond switch:

  • Value binding: capture parts of a matched value (let x, let y).
  • Ranges and intervals: 1...5, ..<10, Character("a")..."z".
  • Tuples: match structure and selectively ignore with _.
  • where clauses: add extra conditions.
let number = 42
switch number {
case let n where n.isMultiple(of: 2):
    print("\(n) is even")
default:
    break
}
  • Optional patterns: case .some(let value) or simply case let value?.
  • if case / guard case / for case: pattern match outside switch.
if case let .some(v) = Optional(5) { print(v) }

for case let .success(value) in results {
    print("Success:", value)
}

Patterns make code expressive: instead of checking and unwrapping manually, you tell the compiler the shape you expect.


7. Control Transfer Statements

These statements alter normal flow inside loops/switches/functions.

continue

Skips the current iteration, moves to the next.

for n in 1...10 {
    if n.isMultiple(of: 2) { continue }
    print(n) // prints odd numbers
}

break

  • In loops: exits the loop immediately.
  • In switch: ends the case early (rarely needed, since cases don’t fall through).
while true {
    let cmd = readLine() ?? ""
    if cmd == "quit" { break }
}

fallthrough

Forces execution to continue to the next switch case. Use sparingly (mainly for simple grouping logic).

let ch: Character = "a"
switch ch {
case "a":
    print("First letter")
    fallthrough
case "b":
    print("One of the first two letters")
default:
    break
}

return

Exits a function/closure. Can return a value:

func square(_ x: Int) -> Int {
    return x * x
}

For single-expression functions, return can be omitted: func square(_ x: Int) -> Int { x * x }.

guard (below) typically ends with a return, throw, continue, or break to satisfy flow rules.

throw

Signals an error has occurred; control jumps to the nearest catch. Functions that can throw must be marked throws.

enum ValidationError: Error { case empty }

func validate(_ s: String) throws {
    if s.isEmpty { throw ValidationError.empty }
}

do {
    try validate("")
} catch {
    print("Invalid:", error)
}
  • Use try? to convert errors into optionals, try! to assert no error (crashes if wrong).
  • Combine with guard for early exits on failure.

8. Early Exits with guard

guard is Swift’s “happy path first” tool. You assert what must be true to continue; otherwise you exit.

func greet(_ name: String?) {
    guard let name = name, !name.isEmpty else {
        print("No name to greet"); return
    }
    print("Hello, \(name)!")
}
  • Condition must lead to an exit in the else block.
  • Bound variables (name above) remain available after the guard.
  • Makes functions flatter and easier to read.

9. Checking API Availability

On Apple platforms, you often need to gate code by OS version. Swift provides compile-time syntax:

if #available(iOS 17, macOS 14, *) {
    // Use new API
} else {
    // Fallback for older systems
}
  • The trailing * means “on all other platforms, assume availability”.
  • Works in global, function, or local scope.
  • Can be combined with guard:
guard #available(iOS 16, *) else {
    // Provide alternative path
    return
}
useNewStuff()

You can also mark declarations:

@available(iOS 15, *)
func newFeature() { /* ... */ }

Calling newFeature() from code that runs on earlier iOS requires an availability check.


10. Putting It Together: A Mini Example

enum FetchError: Error { case networkDown, badData }

func process(scores: [Int]?) throws -> Double {
    // Early exit if no data
    guard let scores, !scores.isEmpty else { throw FetchError.badData }

    var total = 0
    for score in scores {
        if score < 0 { continue }      // skip invalid
        total += score
        if total > 1_000 { break }      // cap work
    }

    let avg = Double(total) / Double(scores.count)

    switch avg {
    case 0..<50:    print("Low")
    case 50..<80:   print("Medium")
    case 80...100:  print("High")
    default:        print("Out of range")
    }

    return avg
}

if #available(iOS 17, *) {
    // New APIs only if iOS 17+
}

This snippet shows: optional binding, guard exit, for-in with continue/break, switch with ranges, and error throwing.


11. Quick Mental Cheatsheet

  • Branching: if/else, switch, pattern matching (if case, guard case).
  • Loops: for-in (preferred), while, repeat-while.
  • Early exits: guard + return/throw/break/continue.
  • Transfer statements: continue, break, fallthrough, return, throw.
  • Availability: if #available, @available.

 

YOU MIGHT ALSO LIKE...
🧭 NavigationKit

NavigationKit is a lightweight library which makes SwiftUI navigation super easy to use. 💻 Installation 📦 Swift Package Manager Using Swift Package Manager, add ...

swiftui-navigation-stack

An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation. NavigationStack Installation ...

Stinsen

Simple, powerful and elegant implementation of the Coordinator pattern in SwiftUI. Stinsen is written using 100% SwiftUI which makes it ...

SwiftUI Router

With SwiftUI Router you can power your SwiftUI app with path-based routing. By utilizing a path-based system, navigation in your app becomes ...

FlowStacks

This package takes SwiftUI's familiar and powerful NavigationStack API and gives it superpowers, allowing you to use the same API not just ...