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.

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:

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

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

9. guard vs if, and break vs continue

guard vs if

Both guard and if check conditions, but they emphasize different reading styles.

StatementBest forWhat happens when condition fails
guardRequired conditions and early exitsThe current scope must exit
ifBranching between possible pathsExecution 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.

StatementEffect
breakEnds the entire loop immediately
continueSkips 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

12. Practice Exercise

Try writing a function that processes a list of numbers and prints only the positive even values.

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.