Control Flow
Master if/else, Swift's powerful switch statement with ranges and where clauses, for-in and while loops, and control transfer statements.
On this page 13 sections
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
switchmust be exhaustive — leaving out adefault(when needed) is a compile-time error, not a silent skip. - Assuming
switchfalls through automatically like C or JavaScript — it doesn't; use the explicitfallthroughkeyword if you truly need that behavior. - Mixing up
...(closed range, includes both ends) and..<(half-open range, excludes the upper bound). - Writing a
guardstatement whoseelseblock doesn't actually exit the current scope — the compiler will reject it.
Best Practices
- Prefer
switchover longif/else ifchains once you're checking more than two or three discrete cases. - Use
guardfor validating preconditions at the top of a function — it keeps the "happy path" unindented and readable. - Reach for
whereclauses inswitchwhen a case needs an extra condition, instead of nesting anifinside 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.