Skip to content

05 · Optionals Basics

The problem optionals solve

In many languages, any reference can silently be null, and the compiler doesn't remind you to check. Swift makes the possible absence of a value part of the type system: a plain String is guaranteed to hold a string; an String? ("optional String") might hold a string, or might hold nothing.

var name: String? = "Ada"
name = nil   // legal -- name is optional, so it can hold "no value"

// var required: String = "Ada"
// required = nil   // compile error -- non-optional types can never be nil

Declaring and unwrapping with ! (force unwrap)

let possibleNumber: String = "42"
let convertedNumber: Int? = Int(possibleNumber)   // Int("42") returns Int?

print(convertedNumber!)   // 42 -- force-unwrap: "I'm certain this isn't nil"

let badNumber: Int? = Int("not a number")
// print(badNumber!)   // would crash at runtime: "Fatal error: Unexpectedly found nil"

Force-unwrapping with ! is a promise to the compiler that you've already ruled out nil — if you're wrong, the program crashes immediately. Treat ! as a last resort, not a habit.

Safe unwrapping with if let

let possibleNumber: String = "abc"
let convertedNumber = Int(possibleNumber)

if let actualNumber = convertedNumber {
    print("It's a number: \(actualNumber)")
} else {
    print("Not a valid number")
}
// Not a valid number

Since Swift 5.7, when the unwrapped constant has the same name as the optional, you can shorten this:

let convertedNumber: Int? = Int("42")

if let convertedNumber {
    print("Got \(convertedNumber)")   // Got 42
}

guard let — early exit

guard reads like an "or else, bail out" statement, and is the idiomatic choice inside functions: it keeps the happy path unindented and unwraps the value for use in the rest of the function, not just an inner block.

func greet(_ name: String?) -> String {
    guard let name else {
        return "Hello, stranger!"
    }
    // "name" is a non-optional String from here to the end of the function
    return "Hello, \(name)!"
}

print(greet("Grace"))   // Hello, Grace!
print(greet(nil))        // Hello, stranger!

Nil-coalescing operator ??

Provide a default value to fall back to when an optional is nil, in a single expression:

let storedName: String? = nil
let displayName = storedName ?? "Guest"
print(displayName)   // Guest

let savedScore: Int? = 95
let effectiveScore = savedScore ?? 0
print(effectiveScore)   // 95

Optional chaining

Access properties/methods through an optional with ?. — if any link in the chain is nil, the whole expression short-circuits to nil instead of crashing.

struct Address {
    let city: String
}
struct Person {
    let name: String
    let address: Address?
}

let personWithAddress = Person(name: "Ada", address: Address(city: "London"))
let personWithoutAddress = Person(name: "Alan", address: nil)

print(personWithAddress.address?.city)      // Optional("London")
print(personWithoutAddress.address?.city)   // nil

// Combine with ?? for a plain, non-optional result:
print(personWithoutAddress.address?.city ?? "Unknown")   // Unknown

Implicitly unwrapped optionals

String! behaves like String? but is auto-unwrapped on access, without ! at each use site. Reserved for rare cases (like properties set right after initialization but before first use) — regular optionals are almost always the better choice.

var window: String! = "Main Window"
print(window.count)   // used directly, no "!" or "?" needed -- 11

Cheat sheet

Technique Syntax When to use
Force unwrap value! Only when nil is truly impossible; crashes otherwise
Optional binding if let x = value { ... } Use the value only inside this block
Early exit guard let x = value else { return } Use the value for the rest of the function
Default value value ?? defaultValue One-line fallback, no branching needed
Optional chaining value?.property Safely reach through a chain of optionals
Implicitly unwrapped String! Rare; auto-unwraps, still crashes if nil

How It Actually Works

  • Optional<T> is not compiler magic — it is a plain generic enum in the standard library with two cases, .some(Wrapped) and .none. nil is just syntactic sugar for .none resolved through ExpressibleByNilLiteral.
  • For most types, the enum is not free: it needs an extra tag bit somewhere to distinguish "no value" from "a value." Swift's compiler exploits spare bit optimization: for a class reference, the pointer can never legitimately be all zero bits at the represented range, so the compiler reuses an otherwise invalid bit pattern to represent .none, making Optional<SomeClass> the exact same size as the raw pointer — no extra memory, no extra branch beyond a bit test.
  • Force-unwrap (!) compiles to a runtime check (swift_unexpectedError-style trap) that calls fatalError-equivalent machinery if the enum's tag is .none — this is why a force-unwrap crash shows a real stack trace and not undefined behavior; Swift always checks first.
  • Optional chaining (?.) compiles into a short-circuiting sequence of conditional branches at the SIL level, functionally identical to writing nested if let yourself — the compiler builds one implicit "did any link in the chain return nil" flag and threads it through, rather than doing anything exotic.
  • ?? (nil-coalescing) is a regular function overloaded on Optional, @autoclosure-annotated on its right-hand side so the default expression is only evaluated (lazily) when the left side is actually .none — that's why value ?? expensiveFallback() doesn't pay for expensiveFallback() on the happy path.

🔀 See this in another language

Exercise

Write a function parseAge(_ input: String) -> Int? that uses Int(input) to attempt a conversion. Then write describeAge(_ input: String) -> String that calls parseAge, uses guard let to bail out with "Invalid age" if parsing fails, and otherwise returns "You are N years old" — where ages under 0 or over 150 should also count as invalid.