Skip to content

5. Booleans & conditionals

What & why

Programs make decisions. Swift's Bool type, if, switch, and guard give you precise, hard-to-misuse ways to branch. In particular, switch is far more powerful than in C-family languages — it's one of Swift's signature features.

Bool and comparison

let age = 20
print(age >= 18)        // true
print(age == 20)        // true
print(age != 20)        // false
print("a" < "b")        // true

Comparison operators: == != < <= > >=. Conditions must be a real Bool — Swift has no "truthy" values:

let count = 3
// if count { }         // ERROR: 'Int' is not convertible to 'Bool'
if count != 0 { print("non-zero") }

Logical operators

let hasTicket = true
let isVIP = false

print(hasTicket && isVIP)     // false  (and)
print(hasTicket || isVIP)     // true   (or)
print(!hasTicket)             // false  (not)

&& and || short-circuit: the right side runs only if needed.

let items: [Int] = []
if !items.isEmpty && items[0] > 5 { }     // safe: items[0] is never evaluated when empty

Toggle a stored Bool with flag.toggle().

if / else if / else

No parentheses around the condition; braces are mandatory:

let temperature = 28

if temperature > 30 {
    print("hot")
} else if temperature > 20 {
    print("warm")
} else {
    print("cool")
}

if as an expression

Since Swift 5.9, if (and switch) can produce a value:

let label = if temperature > 30 { "hot" } else if temperature > 20 { "warm" } else { "cool" }

Every branch must provide a value of the same type, and else is required.

Ternary operator

let status = age >= 18 ? "adult" : "minor"

Fine for simple choices; use if expressions when it gets long. Don't nest ternaries.

switch

let day = 3

switch day {
case 1:
    print("Monday")
case 2:
    print("Tuesday")
case 3:
    print("Wednesday")
default:
    print("Another day")
}

Differences from C that matter:

  • No fallthrough. Each case ends automatically; no break needed.
  • Exhaustive. Every possible value must be covered — usually with default. (With enums, the compiler checks that you listed every case, no default needed.)
  • Every case must contain at least one statement.

Multiple values, ranges, tuples, where

switch day {
case 1, 2, 3, 4, 5:
    print("Weekday")
case 6, 7:
    print("Weekend")
default:
    print("Invalid")
}

let score = 87
switch score {
case 90...100: print("A")
case 80..<90:  print("B")
case 70..<80:  print("C")
case ..<70:    print("Below C")
default:       print("Out of range")
}

let point = (x: 2, y: 0)
switch point {
case (0, 0):          print("origin")
case (_, 0):          print("on the x-axis")
case (0, _):          print("on the y-axis")
case (let x, let y) where x == y:  print("on the diagonal")
default:              print("somewhere else")
}

Value binding

switch point {
case (let x, 0): print("x-axis at \(x)")
case (0, let y): print("y-axis at \(y)")
case let (x, y): print("(\(x), \(y))")
}

Switching on strings and switch as an expression

let command = "stop"
let code = switch command {
    case "go":   1
    case "stop": 2
    default:     0
}

Need C-style fall-through? Write the fallthrough keyword explicitly — rarely a good idea.

guard — early exit

guard checks a condition that must be true to continue, and requires you to leave the scope if it isn't:

func describe(age: Int) {
    guard age >= 0 else {
        print("Invalid age")
        return
    }
    print("Age is \(age)")
}

It keeps the "happy path" un-nested. You'll use it constantly with optionals (lesson 10).

Comparing with ranges

let r = 1...10        // closed range: 1 through 10
print(r.contains(5))  // true
print(5 ~= r)         // true (the pattern-match operator, used implicitly in switch)

Common mistakes

  • Using = instead of == in a condition: Swift rejects it (= returns Void), which is a safety win.
  • Forgetting default in a switch over an Int or String.
  • Expecting fallthrough from one case to the next.
  • Writing parentheses and semicolons out of habit — legal but unidiomatic.
  • Deep nesting instead of guard early-exits.
  • Comparing floating-point values with == (see lesson 3).

Exercises

  1. Write code that prints whether a year is a leap year (divisible by 4, except centuries not divisible by 400).
  2. Given a score from 0–100, print a letter grade using switch with ranges.
  3. Use switch on a tuple (Int, Int) to classify a point as origin / x-axis / y-axis / quadrant 1–4.
  4. Write func sign(_ n: Int) -> String returning "negative", "zero" or "positive" using an if expression.
  5. Rewrite a nested if pyramid (3 levels deep) using guard early exits.
  6. Read a command ("add", "remove", "list", anything else) with readLine() and respond using switch.

Recap

  • Conditions must be Bool; no truthy/falsy values.
  • && / || short-circuit.
  • if and switch can be expressions that return a value.
  • switch is exhaustive, has no fallthrough, and matches ranges, tuples, bindings and where clauses.
  • guard ... else { return } keeps code flat and safe.

Next: Loops →