Swift Optionals Cheat Sheet
Covers declaring optional types, optional binding, nil-coalescing, optional chaining, and safe unwrapping patterns in Swift.
Declaring Optionals
The ? suffix marks a type as allowed to hold nil.
var name: String? = "Swift" // may hold a String or nilname = nil // valid, since it's optionalvar age: Int = 30 // non-optional, cannot be nil// age = nil // compile error// Force unwrap with ! -- crashes at runtime if nillet forced: String = name!
Optional Binding
Safely unwrapping optionals with if let, guard let, and while let.
var nickname: String? = "Swifty"// if let: unwraps only if non-nil, scoped to the if blockif let nickname = nickname { print("Hello, \(nickname)")} else { print("No nickname")}// Swift 5.7+ shorthand: `if let nickname` reuses the same nameif let nickname { print("Hello, \(nickname)")}// guard let: unwraps or exits the current scope earlyfunc greet(_ nickname: String?) { guard let nickname else { print("No nickname provided") return } print("Hello, \(nickname)") // nickname is non-optional here}// while let: loop while unwrapping succeedsvar stack = [1, 2, 3]while let top = stack.popLast() { print(top)}
Nil-Coalescing & Chaining
Providing defaults and safely accessing chains of optionals.
struct Address { var city: String? }struct Person { var address: Address? }let person = Person(address: Address(city: nil))// Optional chaining: returns nil if any link in the chain is nillet city: String? = person.address?.city// Nil-coalescing operator: provide a default for a nil optionallet displayCity = person.address?.city ?? "Unknown"// Chaining with method callslet uppercased = person.address?.city?.uppercased() ?? "N/A"
Unwrapping Methods
The full toolkit for safely handling optional values.
- if let- Conditionally unwraps; the unwrapped constant is scoped to the if block
- guard let- Unwraps or exits the enclosing scope; keeps the unwrapped value in outer scope
- ?? (nil-coalescing)- Supplies a default value when the optional is nil
- ?. (optional chaining)- Accesses a member only if the receiver is non-nil, short-circuits to nil otherwise
- ! (force unwrap)- Crashes with a runtime trap if the optional is nil; use only when certain
- as?- Optional (conditional) type cast, returns nil instead of crashing on failure
- Optional map/flatMap- .map transforms the wrapped value if present; .flatMap avoids nested optionals
Implicitly Unwrapped Optionals
A special optional that behaves like non-optional at use sites.
// Implicitly unwrapped optional: behaves like a non-optional at use sites,// but can still be nil and crashes if accessed while nil.var window: UIWindow!// Common for properties set immediately after init (e.g. IBOutlets)class ViewController: UIViewController { @IBOutlet var titleLabel: UILabel! // set by Interface Builder before use}// Prefer regular optionals or ensuring init sets the value directly// wherever possible -- implicitly unwrapped optionals reintroduce crash risk.
Pattern Matching Optionals in switch
Using case let and the .some/.none enum cases to branch on optionality inside a switch statement.
let response: Int? = 404switch response {case .some(let code) where code >= 400: print("Error code: \(code)")case .some(let code): print("Success code: \(code)")case .none: print("No response")}// Shorthand `code?` pattern is equivalent to `.some(let code)`switch response {case let code? where code >= 500: print("Server error: \(code)")case let code?: print("Got: \(code)")case nil: print("Nothing")}
compactMap and Optional flatMap
Removing nils from a transformed collection, and flattening nested optionals without pyramids.
let raw = ["1", "two", "3", "four", "5"]// compactMap drops nil results and unwraps the rest in one passlet numbers = raw.compactMap { Int($0) } // [1, 3, 5]// Optional.map transforms a wrapped value but re-wraps in another optional,// which can produce Int?? -- flatMap collapses the double optional to Int?let maybeString: String? = "42"let nestedResult: Int?? = maybeString.map { Int($0) } // Int??let flatResult: Int? = maybeString.flatMap { Int($0) } // Int?// compactMap also works to strip nils out of an array of optionals directlylet mixed: [Int?] = [1, nil, 3, nil, 5]let present = mixed.compactMap { $0 } // [1, 3, 5]
Advanced Optional Idioms
Patterns and pitfalls that go beyond basic unwrapping.
- try? converts throws to Optional- `try? someThrowingCall()` discards the error and yields nil on failure instead of propagating
- Double optionals (T??)- Can arise from `as?` on an already-optional value or nested map calls; usually a sign to use flatMap or restructure
- unsafelyUnwrapped- Like `!` but skips the nil check in release builds for a small perf gain; only safe when nil is provably impossible
- zip(_:_:) with optionals- No built-in `zip` for optionals, but a custom `func zip<A,B>(_ a: A?, _ b: B?) -> (A,B)?` is a common utility to combine two optionals only when both are non-nil
- Optional conforms to Equatable/Comparable conditionally- `Optional<Wrapped>` gains `==` and `<` only when Wrapped itself conforms, enabling direct comparisons like `a == nil` or `1 < someOptionalInt`
- @autoclosure in ??- The right-hand side of `??` is `@autoclosure` and lazily evaluated, so `dict[key] ?? expensiveDefault()` only calls expensiveDefault() when the lookup is nil
- Optional in generic constraints- Writing `func firstNonNil<T>(_ values: T?...) -> T?` lets a single generic function accept any number of optionals of the same wrapped type
try? / try! and Error-to-Optional Bridging
Converting throwing function results into optionals for lighter-weight error handling.
enum ParseError: Error { case invalidFormat }func parseAge(_ text: String) throws -> Int { guard let value = Int(text) else { throw ParseError.invalidFormat } return value}// try? turns a thrown error into nil, folding the Result into an Optionallet age: Int? = try? parseAge("42")let bad: Int? = try? parseAge("oops") // nil, error is silently discarded// Chaining try? with ?? supplies a fallback in one linelet safeAge = (try? parseAge("oops")) ?? 0// try! force-unwraps the success case and crashes on any thrown error --// use only when failure is truly impossible, same contract as `!`let mustParse = try! parseAge("99")
Extending Optional and a Custom zip Helper
Adding your own utilities onto Optional<Wrapped> for domain-specific unwrapping logic.
extension Optional { // Runs a closure for the side effect only when the value is present, // returning self unchanged -- useful for logging in a chain @discardableResult func onSome(_ action: (Wrapped) -> Void) -> Wrapped? { if case let .some(value) = self { action(value) } return self }}func zip<A, B>(_ a: A?, _ b: B?) -> (A, B)? { guard let a, let b else { return nil } return (a, b)}let name: String? = "Ana"let age: Int? = 30if let pair = zip(name, age) { print("\(pair.0) is \(pair.1)")}name.onSome { print("Have a name: \($0)") }
Prefer `guard let` at the top of a function over nested `if let` pyramids — it unwraps once, keeps the non-optional value usable for the rest of the function, and makes the early-exit failure path explicit and easy to scan.