LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
Lesson 517 min read

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")
}
Output

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")
}
Output

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")
}
Output

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 = 0
while count < 3 {
print("count is \(count)")
count += 1
}
var attempts = 0
repeat {
attempts += 1
} while attempts < 1
print("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)
}
Output

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)
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These 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.

Next Lesson →

Functions & Closures