Optional Chaining
Reviewed & published by Brayan K
Reach deep into nested optionals with the ?. operator. If any link is nil, the whole expression safely becomes nil instead of crashing — collapsing a pyramid of if lets into one clean line.
Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • Chain property access with a?.b?.c
- • Understand why chaining always returns an optional
- • See how chaining flattens nested optionals (no Int??)
- • Call methods and use subscripts safely through the chain
- • Combine chaining with ?? for clean defaults
- • Know when ?. beats force-unwrapping with !
1️⃣ The Basics — a?.b
Put a ? after an optional, then a . to access something on it. If the optional is nil, the access is skipped and the result is nil. The result is always optional, so pair it with ?? to get a usable value.
// Optional chaining lets you reach into an optional with ?. — and if any
// link is nil, the whole expression just becomes nil instead of crashing.
class Address { var city: String? }
class Person { var address: Address? }
let p = Person()
// p.address is nil right now, so the chain short-circuits to nil.
let city1 = p.address?.city // type: String?, value: nil
print(city1 as Any) // nil
// Give it some data and try again.
let a = Address()
a.city = "London"
p.address = a
let city2 = p.address?.city // String?, value: Optional("London")
print(city2 as Any) // Optional("London")
// The RESULT is always optional, even reaching a non-optional property.
// Provide a default with nil-coalescing:
let display = p.address?.city ?? "Unknown"
print(display) // London2️⃣ Chaining Flattens, and ?. vs !
When the final property is itself optional, chaining doesn't pile up ?? — it flattens to a single optional. And unlike !, which crashes on nil, ?. fails gracefully.
// Optional chaining FLATTENS — you never end up with Int?? or String??.
class Locker { var contents: String? }
class Bag { var locker: Locker? }
let bag = Bag()
// 'contents' is already String?. Reaching it via chaining stays String?,
// NOT String??.
let item: String? = bag.locker?.contents
print(item as Any) // nil (bag.locker is nil)
// Compare ?. (safe) with ! (force-unwrap, crashes on nil):
let safe = bag.locker?.contents // String? -> nil, no crash
print(safe as Any) // nil
// let boom = bag.locker!.contents // ❌ would CRASH: locker is nil3️⃣ Methods and Subscripts
Chaining isn't just for properties. You can call methods (obj?.method()) and use subscripts (obj?[i], with the ? before the bracket). If the receiver is nil, the call simply doesn't happen.
// You can also call METHODS and use SUBSCRIPTS through the chain.
class Team {
var players: [String] = ["Ada", "Sam"]
func captain() -> String { players[0] }
}
class Club { var team: Team? }
let club = Club()
print(club.team?.captain() as Any) // nil — team is nil, call is skipped
print(club.team?.players[0] as Any) // nil — subscript skipped too
club.team = Team()
print(club.team?.captain() as Any) // Optional("Ada") — method ran
print(club.team?.players[1] as Any) // Optional("Sam") — subscript ran
// A Void method returns Void? — handy to check if it actually ran.
class Logger { func log() { print("logged!") } }
var maybeLogger: Logger? = nil
if maybeLogger?.log() != nil {
print("ran")
} else {
print("skipped") // prints: skipped
}Your turn. Fill in the chaining operator and the default operator.
// 🎯 YOUR TURN — fill in each ___ then run it on SwiftFiddle.
class Engine { var horsepower: Int? }
class Car { var engine: Engine? }
let car = Car()
// 1) Reach the horsepower safely. If any link is nil, give 0 as a default.
let hp = car.engine___horsepower ___ 0 // 👉 chaining operator, then default operator
print("Horsepower: \(hp)") // Horsepower: 0
// Now fill the car in and try again.
let e = Engine()
e.horsepower = 300
car.engine = e
let hp2 = car.engine?.horsepower ?? 0
print("Horsepower: \(hp2)") // Horsepower: 300
// ✅ Expected output:
// Horsepower: 0
// Horsepower: 300Pro Tips
- 💡 Pair with ??. A chain returns an optional — give it a sensible default immediately.
- 💡 One ? per optional link. a?.b?.c short-circuits at the first nil.
- 💡 Subscript placement. Write arr?[0], not arr[0]?.
- 💡 Prefer ?. over ! whenever a link could legitimately be missing.
Common Errors (and the fix)
- Treating the result as non-optional: a chain returns T?. Unwrap with if let or supply a default with ??.
- "Cannot use optional chaining on non-optional value": you put ? after something that isn't optional. Drop it.
- Crash from !: you force-unwrapped a nil link. Switch the ! to ?.
- Wrong subscript syntax: the ? goes before the bracket: arr?[i].
📋 Quick Reference
| Task | Code | Result type |
|---|---|---|
| Property | a?.b | optional |
| Deep chain | a?.b?.c | optional |
| Method | a?.run() | optional |
| Subscript | arr?[0] | optional |
| With default | a?.b ?? x | non-optional |
Mini-Challenge: Nested Profile
No blanks this time — just a brief and an outline. Build the nested classes and reach the deepest value with a single chaining expression.
// 🎯 MINI-CHALLENGE: nested profile
// 1. Make classes: Country { var name: String? },
// City { var country: Country? }, User { var city: City? }
// 2. Create a User with no city set.
// 3. Print the country name with one optional-chaining expression,
// defaulting to "Unknown" when any link is nil.
// 4. Then populate city -> country -> name = "France" and print again.
//
// ✅ Expected:
// Unknown
// France
// your code here
Practice quiz
What does optional chaining with ?. do when a link in the chain is nil?
- Crashes the program
- Returns a default value of 0
- Stops and returns nil for the whole expression
- Throws an error
Answer: Stops and returns nil for the whole expression. If any link is nil, the chain short-circuits and the whole expression evaluates to nil — no crash.
What is the type of the result of an optional chaining expression like a?.b where b is an Int?
- Int?
- Int
- Bool
- Optional<Optional<Int>>
Answer: Int?. Optional chaining always wraps the result in an optional, so accessing an Int property gives Int?.
If a property is already optional and you access it via chaining, does the result become doubly optional?
- Yes, it becomes Int??
- It becomes a non-optional
- It crashes
- No, optional chaining flattens it to a single level of optionality
Answer: No, optional chaining flattens it to a single level of optionality. Optional chaining flattens the result — you never get Int??; it stays a single Int?.
How does ?. differ from ! when accessing a property on an optional?
- They are identical
- ?. fails gracefully to nil; ! force-unwraps and crashes if nil
- ?. is faster
- ! returns nil
Answer: ?. fails gracefully to nil; ! force-unwraps and crashes if nil. ?. safely returns nil on a nil link, while ! crashes if the optional is nil.
Can you call a method through optional chaining, like person?.greet()?
- Yes — and the call simply doesn't happen if person is nil
- No, only properties
- Only on classes
- Only if greet returns nil
Answer: Yes — and the call simply doesn't happen if person is nil. Optional chaining works on method calls and subscripts too; the call is skipped when the receiver is nil.
What does the result of calling a Void-returning method via optional chaining (person?.printName()) become?
- Void
- Bool
- Void? — so you can check if it ran by comparing to nil
- Int?
Answer: Void? — so you can check if it ran by comparing to nil. Even a Void method returns Void? through chaining, letting you check 'if person?.printName() != nil' to see if it executed.
How do you write a subscript access through optional chaining?
- array?[0]
- array?.[0]
- array![0]
- array.?[0]
Answer: array?.[0]. The question mark goes before the subscript bracket: optionalArray?[0].
What is a clean way to provide a fallback when an optional chain returns nil?
- Use ! after the chain
- Use a for loop
- Use guard inside the chain
- Use the nil-coalescing operator ?? after the chain
Answer: Use the nil-coalescing operator ?? after the chain. Combine chaining with ??: person?.address?.city ?? "Unknown".
In a?.b?.c, if 'a' has a value but 'b' is nil, what happens to '.c'?
- It still evaluates
- It is skipped and the whole expression is nil
- It crashes
- It returns c's default
Answer: It is skipped and the whole expression is nil. The chain short-circuits at the first nil link, so .c is never reached and the result is nil.
Why is optional chaining preferred over nested if-let pyramids?
- It is the only way to unwrap
- It never returns nil
- It expresses a deep, possibly-nil access in one readable line
- It is required by the compiler
Answer: It expresses a deep, possibly-nil access in one readable line. Chaining collapses several nested unwraps into a single concise expression that reads left to right.
Continue this course
- Previous: guard & defer
- Next: Type Casting (is, as?) — Check and convert types with is, as?, as! and Any/AnyObject
Frequently asked questions
What is optional chaining in Swift?
Optional chaining is a way to access a property, method, or subscript on something that might be nil, using the ?. operator. If the optional has a value, the access proceeds; if it is nil, the whole expression short-circuits and evaluates to nil instead of crashing. It lets you reach deep into nested optionals — like a?.b?.c — in a single safe line.
Why is the result of optional chaining always an optional?
Because the access might not happen at all (a link could be nil), Swift wraps the result in an optional to represent 'either the value, or nil because the chain broke'. So even if you chain to a non-optional Int property, you get back Int?. Use the nil-coalescing operator ?? to supply a default and unwrap it in one step.
Does chaining to an already-optional property give me a double optional?
No. Optional chaining flattens the result, so you always end up with a single level of optionality. If b is already String? and you write a?.b, the result is String?, never String??. This keeps long chains easy to work with.
How is ?. different from force-unwrapping with !?
The ! operator force-unwraps and crashes immediately if the value is nil. The ?. operator does the opposite: it fails gracefully, producing nil for the whole expression when a link is nil. Prefer ?. for anything that could legitimately be missing, and reserve ! for cases where nil would be a genuine programming error.
Can I call methods and use subscripts through optional chaining?
Yes. You can write person?.greet() to call a method only when person is non-nil, and array?[0] to subscript only when array is non-nil — note the ? comes before the bracket. If the receiver is nil, the call or subscript is simply skipped and the expression is nil. Even a Void-returning method yields Void?, which you can compare to nil to see whether it actually ran.