Skip to content

10. Optionals

What & why

Some values legitimately might not exist: a dictionary lookup that finds nothing, a string that doesn't parse as a number, the first element of an empty array. Many languages use null for this and let it crash you later. Swift makes absence part of the type: Int always holds an integer; Int? holds either an integer or nothing. The compiler forces you to handle the "nothing" case before you can use the value.

This is Swift's most important safety feature. Learn it well.

Declaring optionals

var nickname: String? = nil      // no value
nickname = "Ace"                 // has a value

let number = Int("123")          // Int?   — Optional(123)
let bad = Int("abc")             // Int?   — nil

String? is shorthand for Optional<String>, which is just an enum with two cases: .none (written nil) and .some(value).

You can't use an optional like a regular value:

let n = Int("5")
// print(n + 1)    // ERROR: value of optional type 'Int?' must be unwrapped

Unwrapping, safest first

1. if let — use it only if it exists

if let n = Int("42") {
    print("Parsed \(n)")        // n is a plain Int in here
} else {
    print("Not a number")
}

// Shorthand when the new name equals the old one (Swift 5.7+):
let age: Int? = 30
if let age {
    print("Age \(age)")
}

Combine several in one if:

if let a = Int("3"), let b = Int("4"), a < b {
    print(a + b)
}

2. guard let — exit early, keep the value afterwards

func parseAge(_ text: String) -> Int? {
    guard let value = Int(text) else {
        return nil
    }
    guard value >= 0 else { return nil }
    return value                 // `value` is available for the rest of the function
}

guard let is the best choice when "no value" means "stop here."

3. ?? — nil-coalescing: provide a default

let input: String? = nil
let name = input ?? "Anonymous"          // "Anonymous"
let port = Int("abc") ?? 8080            // 8080

4. Optional chaining ?. — keep going, or become nil

let text: String? = "swift"
print(text?.uppercased())        // Optional("SWIFT")
print(text?.count ?? 0)          // 5

let missing: String? = nil
print(missing?.uppercased())     // nil — the whole chain short-circuits

A chain's result is always optional. Chains can be as long as you like: user?.address?.city?.count.

5. map and flatMap on optionals

let length = text.map { $0.count }                  // Int?   (nil if text is nil)
let parsed = Optional("12").flatMap { Int($0) }     // Int?   (flatMap avoids Int??)

6. Force unwrapping ! — last resort

let value = Int("42")!     // crashes with "unexpectedly found nil" if the parse fails

Only use ! when you can prove it can't be nil (for example, a hard-coded literal you control, or right after checking). If you're not sure, use one of the safe forms above. A crash in production is a much worse failure than writing two more lines.

Similarly, let x = y! on a user-supplied value is a bug waiting to happen.

Implicitly unwrapped optionals (Type!)

var label: String! is an optional that auto-unwraps when used. You'll see it in older UI code (outlets set after init). Avoid it in your own code.

Optional pattern matching

let score: Int? = 87

switch score {
case .some(let s) where s >= 90: print("great")
case .some(let s):               print("score \(s)")
case .none:                      print("no score")
}

// Equivalent shorthand:
switch score {
case let s?: print("has \(s)")
case nil:    print("none")
}

Optionals and collections

let names = ["Ada", "Grace"]
print(names.first ?? "nobody")       // "Ada"
print(names.max() ?? "")             // "Grace"

let strings = ["1", "two", "3"]
let numbers = strings.compactMap { Int($0) }    // [1, 3] — nil results are discarded

let dict = ["a": 1]
print(dict["z"] ?? 0)                // 0

Optionals as return values: designing APIs

Return an optional when absence is normal and expected ("no such user"). If something has gone wrong and the caller should know why, use throws (next lesson) instead.

func firstNegative(in numbers: [Int]) -> Int? {
    for n in numbers where n < 0 { return n }
    return nil
}

Comparing optionals

let a: Int? = 5
print(a == 5)        // true — Swift wraps the 5 for you
print(a == nil)      // false
print(a! > 3)        // use unwrap first for ordering comparisons
print(a ?? 0 > 3)    // true

== works if the wrapped type is Equatable.

Common mistakes

  • Reaching for ! to silence the compiler. Pick if let, guard let, or ?? instead.
  • Double optionals (Int??) from map where flatMap was wanted.
  • Shadowing confusion: if let name = name is fine and idiomatic; just remember the inner name is the unwrapped value.
  • Using an optional where a default would do — don't make something optional "just in case."
  • Printing optionals by accident: print(Int("5")) shows Optional(5). Unwrap first.
  • Using nil with non-optional types: var x: Int = nil doesn't compile.

Exercises

  1. Write func safeDivide(_ a: Int, _ b: Int) -> Int? returning nil when b == 0. Call it with if let.
  2. Given ["10", "x", "30"], produce the sum of the valid numbers using compactMap and reduce.
  3. Write func initials(first: String?, last: String?) -> String that handles any combination of nil values.
  4. Given a dictionary [String: [Int]], print the maximum of scores["ada"] or "no scores" using optional chaining and ??.
  5. Rewrite this using guard let: nest 3 if let levels that parse width, height, and depth from strings, then multiply them.
  6. Explain, in one sentence each, when you'd use ??, if let, guard let, ?., and !.

Recap

  • T? means "a T or nil"; the compiler forces you to deal with nil.
  • Unwrap with if let / guard let, supply defaults with ??, chain with ?..
  • map/flatMap/compactMap work with optionals and collections of optionals.
  • ! is a crash waiting to happen — use it only when nil is truly impossible.
  • Return optionals for "normal absence"; use throws for failure with a reason.

Next: Files & error handling →