Control Flow
Master if/else, Swift's powerful switch statement with ranges and where clauses, for-in and while loops, and control transfer statements.
Introduction
Control flow statements decide which code runs, and how many times. Swift's conditionals and loops will feel familiar if you've used another C-family language, but Swift's `switch` statement in particular is far more powerful than its equivalents elsewhere, supporting ranges, tuples, and conditional pattern matching directly.
This lesson covers `if`/`else`, the full power of `switch`, `for-in` and `while` loops, and the statements you use to jump around inside them.
if / else
An `if` statement in Swift does not require parentheses around the condition, but the curly braces are always mandatory — even for a single statement.
let temperature = 72
if temperature > 85 { print("It's hot")} else if temperature > 60 { print("It's pleasant")} else { print("It's cold")}Click Run to see what this code prints.
The switch Statement
Swift's `switch` must be exhaustive — it has to handle every possible case, either explicitly or with a `default` clause, which the compiler enforces. Unlike C, Java, or JavaScript, a matched case does not automatically fall through into the next one.
let grade = "B"
switch grade {case "A": print("Excellent")case "B", "C": print("Good")default: print("Needs improvement")}Click Run to see what this code prints.
Ranges and where Clauses
Swift's `switch` can match numeric ranges directly, and a `where` clause lets you attach an extra condition to a case for genuinely powerful pattern matching.
let score = 82
switch score {case 90...100: print("A")case 80..<90 where score % 2 == 0: print("B (even score)")case 80..<90: print("B")default: print("C or below")}Click Run to see what this code prints.
The `...` operator creates a closed range that includes both ends (90 through 100 inclusive), while `..<` creates a half-open range that excludes the upper bound (80 up to, but not including, 90).
for-in Loops
The `for-in` loop iterates over sequences — arrays, ranges, dictionaries, and any other type conforming to `Sequence`.
let fruits = ["Apple", "Mango", "Banana"]for fruit in fruits { print(fruit)}
for i in 1...5 { print("Count: \(i)")}while and repeat-while
A `while` loop checks its condition before each iteration; `repeat-while` (Swift's version of `do-while`) always runs the body at least once, checking the condition afterward.
var count = 0while count < 3 { print("count is \(count)") count += 1}
var attempts = 0repeat { attempts += 1} while attempts < 1print("attempts: \(attempts)")Control Transfer Statements
Swift provides `break` and `continue` like most C-family languages, plus an explicit `fallthrough` keyword for the rare case where you actually want a `switch` case to fall into the next one.
for number in 1...10 { if number % 2 != 0 { continue } // skip odd numbers if number > 6 { break } // stop once we pass 6 print(number)}Click Run to see what this code prints.
A First Look at guard
Swift also has a `guard` statement for early exits: it checks a condition, and if it fails, the `else` block must exit the current scope (with `return`, `break`, `continue`, or `throw`). `guard` is especially powerful when combined with Optionals — you'll see that fully in the Optionals lesson.
func checkAge(_ age: Int) { guard age >= 18 else { print("Must be 18 or older") return } print("Access granted")}checkAge(15)checkAge(20)Click Run to see what this code prints.
Common Mistakes
- Forgetting that Swift's `switch` must be exhaustive — leaving out a `default` (when needed) is a compile-time error, not a silent skip.
- Assuming `switch` falls through automatically like C or JavaScript — it doesn't; use the explicit `fallthrough` keyword if you truly need that behavior.
- Mixing up `...` (closed range, includes both ends) and `..<` (half-open range, excludes the upper bound).
- Writing a `guard` statement whose `else` block doesn't actually exit the current scope — the compiler will reject it.
Best Practices
- Prefer `switch` over long `if`/`else if` chains once you're checking more than two or three discrete cases.
- Use `guard` for validating preconditions at the top of a function — it keeps the "happy path" unindented and readable.
- Reach for `where` clauses in `switch` when a case needs an extra condition, instead of nesting an `if` inside the case body.
- Choose `..<` when working with array indices (to avoid off-by-one, out-of-bounds errors) and `...` for inclusive counting ranges.
Frequently Asked Questions
It's a deliberate safety choice — accidental fallthrough in C-family languages is a well-known source of bugs. Swift requires you to opt in explicitly with the `fallthrough` keyword.
No, they're optional and generally omitted in idiomatic Swift. The curly braces around the body, however, are always required.
Both can check a condition, but `guard`'s else block is required to exit the current scope, which keeps validation code from nesting your main logic inside an `if` block.
Key Takeaways
- if/else in Swift never requires parentheses but always requires braces.
- switch must be exhaustive, does not fall through automatically, and supports ranges and where clauses.
- for-in iterates over any Sequence; while checks first, repeat-while checks after the first run.
- break, continue, and the explicit fallthrough control loop and switch execution.
- guard performs early-exit validation and must leave the current scope when its condition fails.
Summary
You can now direct the flow of a Swift program with conditionals, powerful pattern-matching switches, and loops. Next, you'll learn how to package logic into reusable functions and closures.