Swift Early Exits: guard, break, continue, and return
Swift gives you several ways to leave the current path of execution early. These early exit tools help you stop a function, skip a loop iteration, leave a loop or switch, or reject invalid conditions before the main logic runs. Learning guard, break, continue, and return makes your code easier to read, safer, and less deeply nested.
Quick answer: In Swift, guard exits the current scope when a required condition fails, break leaves a loop or switch, continue skips to the next loop iteration, and return exits a function or method, optionally sending back a value.
Difficulty: Beginner
You'll understand this better if you know: basic Swift syntax, how if statements and loops work, and simple types like String, Int, and Bool.
1. What Is Swift Early Exits?
Early exits are control flow statements that stop the current path before reaching the bottom of a block. Instead of wrapping everything inside nested conditions, you can leave early when a condition is not valid or when there is nothing more to do.
- guard checks a condition that must be true to continue.
- break stops a loop or exits a switch case.
- continue skips the rest of the current loop iteration.
- return exits a function, method, or computed property.
- These statements reduce nesting and make intent clearer.
These ideas are closely related, but they are not interchangeable. A common beginner confusion is thinking that guard and if do the same thing, or that break and continue both simply “skip” loop work. In practice, each one solves a different control flow problem.
2. Why Early Exits Matter
Early exits matter because real code often has invalid input, edge cases, and stop conditions. If you do not handle those cases early, code becomes deeply nested and harder to follow.
For example, a function that validates user input should stop immediately when the input is empty. A loop that processes only positive numbers should skip negative values without doing extra work. A search loop should stop as soon as the target is found.
Use early exits when:
- A condition must be true before the main logic can continue.
- You want to stop processing once a result is found.
- You want to ignore certain loop values and move on quickly.
- You want to avoid several levels of nested if statements.
Do not use them carelessly just to jump around code. The goal is clarity, not surprise.
3. Basic Syntax or Core Idea
Using guard
A guard statement checks a condition that must be true. If the condition is false, the else block must exit the current scope using something like return, break, continue, or fatalError.
func printUsername(name: String?) {
guard let name = name else {
print("No username provided")
return
}
print("Welcome, \(name)!")
}Here, the function exits immediately if name is nil. If the value exists, the unwrapped name is available after the guard.
Using break
break stops the current loop or exits a switch case early.
for number in 1...10 {
if number == 5 {
break
}
print(number)
}This loop prints 1 through 4, then stops completely when it reaches 5.
Using continue
continue skips the rest of the current loop iteration and moves to the next one.
for number in 1...5 {
if number == 3 {
continue
}
print(number)
}This prints 1, 2, 4, and 5. Only the iteration where the number is 3 is skipped.
Using return
return exits the current function. If the function has a return type, it must return a value of that type.
func square(of number: Int) -> Int {
return number * number
}This function returns the square of the input and then finishes.
4. Step-by-Step Examples
Example 1: Validating input with guard
This example checks whether a password is empty before continuing.
func savePassword(_ password: String) {
guard !password.isEmpty else {
print("Password cannot be empty.")
return
}
print("Password saved.")
}The function stops immediately for invalid input, so the main logic stays simple.
Example 2: Leaving a loop with break
This example searches an array and stops as soon as the target value is found.
let scores = [12, 18, 24, 31, 45]
let target = 31
for score in scores {
if score == target {
print("Found the target score.")
break
}
print("Checked \(score)")
}Once 31 is found, the loop ends. That avoids unnecessary work.
Example 3: Skipping unwanted values with continue
This loop prints only even numbers.
for number in 1...8 {
if number % 2 != 0 {
continue
}
print("Even: \(number)")
}Odd numbers are skipped, so only even numbers reach the print statement.
Example 4: Returning early from a function
This function checks for division by zero before performing the calculation.
func divide(_ left: Double, by right: Double) -> Double? {
guard right != 0 else {
return nil
}
return left / right
}This is a common pattern: reject an invalid case early, then return the real result.
5. Practical Use Cases
- Checking whether a function argument is valid before doing expensive work.
- Unwrapping optional values with guard let before using them.
- Stopping a search loop once a match is found.
- Skipping invalid records while processing a list of input values.
- Leaving a switch case when no action is needed.
- Returning early from functions when permissions, limits, or preconditions fail.
6. Common Mistakes
Mistake 1: Using guard without exiting the scope
A guard statement requires the else block to leave the current scope. Beginners sometimes forget that the failure case cannot simply print a message and continue.
Problem: This code does not exit inside the else block, so Swift reports an error because guard must transfer control out of the scope.
func showAge(_ age: Int?) {
guard let age = age else {
print("Age is missing")
}
print(age)
}Fix: Add a control transfer statement such as return so the invalid path leaves the function.
func showAge(_ age: Int?) {
guard let age = age else {
print("Age is missing")
return
}
print(age)
}The corrected version works because the failure case exits the function before execution can continue.
Mistake 2: Confusing break and continue
break and continue are both used in loops, so it is easy to mix them up. They do very different things.
Problem: This code uses break when the goal is only to skip negative values. As a result, the whole loop stops too early.
let values = [3, 8, -1, 10]
for value in values {
if value < 0 {
break
}
print(value)
}Fix: Use continue when you want to ignore one iteration and keep processing the rest.
let values = [3, 8, -1, 10]
for value in values {
if value < 0 {
continue
}
print(value)
}The corrected version works because the loop skips only the negative value and continues with later items.
Mistake 3: Forgetting to return a value from a non-void function
If a function declares a return type, all code paths must return a value of that type. This often happens when early exits are added but one path is left incomplete.
Problem: This function promises to return an Int, but one path exits the if without returning anything, which causes a compile-time error.
func scoreLabel(_ score: Int) -> Int {
if score < 0 {
print("Invalid score")
}
return score
}Fix: Make every path return a value, or change the return type to an optional if failure should return no value.
func scoreLabel(_ score: Int) -> Int? {
guard score >= 0 else {
print("Invalid score")
return nil
}
return score
}The corrected version works because both the failure and success paths return a value that matches the function signature.
Mistake 4: Using continue outside a loop
continue only makes sense inside loops. It cannot be used in a normal function body or inside an if statement that is not part of a loop.
Problem: This code tries to skip execution with continue even though there is no loop, so Swift reports an error.
func checkName(_ name: String) {
if name.isEmpty {
continue
}
print(name)
}Fix: Use return to leave the function, or restructure the logic if you are not inside a loop.
func checkName(_ name: String) {
if name.isEmpty {
return
}
print(name)
}The corrected version works because return is the proper way to exit a function early.
7. Best Practices
Use guard for required preconditions
When a condition must be true before the rest of the code makes sense, guard is usually clearer than wrapping the main logic inside an if.
Less-preferred deeply nested code:
func sendEmail(to address: String?) {
if let address = address {
print("Sending email to \(address)")
}
}Preferred early-exit version:
func sendEmail(to address: String?) {
guard let address = address else {
return
}
print("Sending email to \(address)")
}This style keeps the success path less indented and easier to scan.
Use break only when you are truly done
If the loop should stop completely, break is correct. If you only want to ignore one value, use continue instead.
let letters = ["a", "b", "stop", "c"]
for letter in letters {
if letter == "stop" {
break
}
print(letter)
}This clearly communicates that stop is a terminating value, not just a skipped one.
Prefer early return to reduce nesting
Many functions are easier to read when invalid or special cases return immediately and the main logic is left at the top level.
func calculateDiscount(for price: Double) -> Double {
guard price > 0 else {
return 0
}
return price * 0.1
}This avoids an unnecessary else block around the main calculation.
8. Limitations and Edge Cases
- guard must exit the current scope in its else block. You cannot leave the block without transferring control.
- break works in loops and switch statements, but it does not exit a function.
- continue works only inside loops. It cannot be used inside a plain if block outside looping code.
- return exits the current function or method, not just a loop inside that function.
- In nested loops, plain break exits only the innermost loop. If you need to leave an outer loop, labeled statements may be necessary.
- Code after an unconditional return, break, or continue in the same path is unreachable and may trigger warnings or show logical mistakes.
9. guard vs if, and break vs continue
guard vs if
Both guard and if check conditions, but they emphasize different reading styles.
| Statement | Best for | What happens when condition fails |
|---|---|---|
| guard | Required conditions and early exits | The current scope must exit |
| if | Branching between possible paths | Execution simply skips the block |
Use guard when the rest of the code should not run unless a condition is true. Use if when you are choosing between actions.
func printMessage(_ text: String?) {
guard let text = text else {
return
}
print(text)
}This reads as “the value must exist, otherwise stop.”
break vs continue
These two are commonly confused in loops.
| Statement | Effect |
|---|---|
| break | Ends the entire loop immediately |
| continue | Skips the current iteration and starts the next one |
If you are done looping, use break. If you are only done with one item, use continue.
10. Practical Mini Project
This mini project processes a list of raw score strings. It uses guard to validate values, continue to skip bad entries, break to stop at a special marker, and return to return the final average.
func averageScore(from items: [String]) -> Double {
var total = 0
var count = 0
for item in items {
if item == "STOP" {
break
}
guard let score = Int(item) else {
print("Skipping invalid value: \(item)")
continue
}
guard score >= 0 && score <= 100 else {
print("Skipping out-of-range score: \(score)")
continue
}
total += score
count += 1
}
guard count > 0 else {
return 0
}
return Double(total) / Double(count)
}
let rawScores = ["80", "95", "-3", "oops", "70", "STOP", "100"]
let result = averageScore(from: rawScores)
print("Average: \(result)")This example shows all four early exit tools working together in a realistic data-processing task. Invalid values are skipped, the special stop marker ends the loop, and the function returns a final result.
11. Key Points
- guard is for conditions that must be true to continue.
- break ends a loop or exits a switch.
- continue skips only the current loop iteration.
- return exits a function or method, optionally with a value.
- Early exits reduce nesting and often make code easier to read.
- guard is especially useful for optional unwrapping and input validation.
- Choose break versus continue based on whether the whole loop should stop or only one item should be skipped.
12. Practice Exercise
Try writing a function that processes a list of numbers and prints only the positive even values.
- Use continue to skip odd numbers.
- Use continue to skip negative numbers.
- Use break if the number 0 appears.
- Use return to exit the function early if the array is empty.
Expected output: For input [4, -2, 7, 10, 0, 12], the function should print 4 and 10, then stop when it reaches 0.
Hint: Check the empty array first, then loop through the numbers in order and decide whether to skip, stop, or print.
func printPositiveEvenNumbers(from numbers: [Int]) {
guard !numbers.isEmpty else {
print("No numbers to process.")
return
}
for number in numbers {
if number == 0 {
break
}
if number < 0 {
continue
}
if number % 2 != 0 {
continue
}
print(number)
}
}13. Final Summary
Swift early exits are simple tools, but they have a big effect on code quality. guard lets you reject invalid conditions early, break ends loops and switch cases, continue skips unneeded loop iterations, and return exits functions cleanly. Used well, they make your code flatter, clearer, and easier to maintain.
The most important idea is to choose the right exit for the job. Use guard when a condition must hold, break when you are done looping, continue when one item should be skipped, and return when the function should end. A great next step is learning Swift conditionals and optionals more deeply, especially if let, guard let, and labeled statements in nested loops.