Optionals & Unwrapping
Sometimes a value is simply not there. A student may have no middle name, a search may find nothing, a user may type "abc" where you expected a number. Many languages use null for this and let any variable be null, which leads to crashes when someone forgets to check.
Swift's answer is the optional. A normal String always contains a string. A String? (read "optional string") contains either a string or nil, meaning "no value". The compiler will not let you use an optional as if it were definitely there, so you must handle the missing case. This lesson teaches every tool for doing that safely.
Declaring optionals
Add ? after a type to make it optional. Optionals start as nil unless you give them a value. Many standard functions return optionals because they can fail.
var middleName: String? = nil
print(middleName)
middleName = "Kumar"
print(middleName)
let pinCode = Int("124001")
print(pinCode)
print(type(of: pinCode))nil
Optional("Kumar")
Optional(124001)
Optional<Int>Printing an optional directly shows Optional(...) around the value, and Xcode warns you about it, because you almost never want users to see that text. String? is shorthand for the type Optional<String>. You cannot write middleName.count here; the compiler insists that you unwrap the optional first.
Unwrapping with if let
Optional binding checks for a value and, if there is one, puts it in a new non-optional constant that you can use inside the braces. Since Swift 5.7 you can write if let name to reuse the same name. You can also bind several values and add conditions, separated by commas.
let typedAge = "19"
if let age = Int(typedAge) {
print("Age next year: \(age + 1)")
}
let nickname: String? = "Ronnie"
if let nickname {
print("Hi, \(nickname)")
}
let typedHeight: String? = "170"
if let text = typedHeight, let height = Int(text), height > 150 {
print("Eligible: \(height) cm")
} else {
print("Not eligible")
}Age next year: 20 Hi, Ronnie Eligible: 170 cm
In the last if, all three parts must succeed: typedHeight must have a value, that text must convert to an Int, and the height must be over 150. If any part fails, the else block runs. Inside the braces, height is a plain Int.
Unwrapping with guard let
guard let unwraps a value for the rest of the function, and exits early when it is missing. It is the best choice when a function cannot do its job without the value.
func shippingLabel(name: String?, pin: String?) -> String {
guard let name, !name.isEmpty else {
return "Name missing"
}
guard let pin, pin.count == 6 else {
return "Invalid PIN for \(name)"
}
return "\(name), PIN \(pin)"
}
print(shippingLabel(name: "Meera", pin: "682001"))
print(shippingLabel(name: "Rohan", pin: "1240"))
print(shippingLabel(name: nil, pin: "302001"))Meera, PIN 682001 Invalid PIN for Rohan Name missing
After each guard, the unwrapped name and pin stay available to the end of the function. Compare this with three levels of nested if let: the guard version keeps the main logic flat and easy to read.
Default values and optional chaining
The nil-coalescing operator ?? gives a default when an optional is nil. Optional chaining with ?. calls a property or method only if the value exists; if it is nil, the whole chain quietly becomes nil.
let students: [String: String] = ["R01": "Aarav", "R02": "Isha"]
print(students["R02"] ?? "Unknown")
print(students["R09"] ?? "Unknown")
print(students["R01"]?.count)
print(students["R09"]?.uppercased() ?? "-")
print(students["R01"]?.uppercased().count ?? 0)Isha Unknown Optional(5) - 5
students["R01"]?.count is still optional (Int?), because the chain could have stopped. That is why it prints Optional(5). Ending a chain with ?? turns it back into a plain value, as the last line does. Chains can be long: school?.principal?.phone?.count stops at the first nil.
Force unwrapping with !
Writing ! after an optional force unwraps it: "I am sure there is a value; give it to me." If you are wrong, the program crashes immediately with the message "Unexpectedly found nil while unwrapping an Optional value".
let pin = Int("110001")!
print(pin)
// let broken = Int("11OO01")! // letter O, not zero: this would crash
let typedPrice: String? = "250"
let withTax = typedPrice.flatMap { Int($0) }.map { $0 * 118 / 100 }
print(withTax ?? 0)110001 295
Use ! only when a nil would be a programmer's mistake that you want to catch loudly, such as a constant you typed into the code yourself. For anything coming from users, files or the network, use if let, guard let or ??.
The second part shows two optional helpers. map transforms the value inside an optional if there is one; flatMap does the same when the transformation itself returns an optional, such as Int($0). The result is still optional, so ?? supplies a fallback.
Optional is just an enum
Behind the syntax, Optional is an ordinary enum with two cases: .some(value) and .none. nil is simply .none. You can even switch over an optional, which you will understand fully after Enums & switch.
let seatsLeft: Int? = 4
switch seatsLeft {
case .some(let count) where count > 0:
print("\(count) seats left")
case .some:
print("Sold out")
case .none:
print("Seat data not loaded")
}4 seats left
Common mistakes
- Printing optionals to users.
"Hello \(name)"with an optional showsOptional("Isha"). Unwrap first or use??. - Using
!to silence the compiler. It turns a compile-time warning into a crash on a user's phone. - Nesting many
if letblocks. Use oneif letwith commas, orguard let. - Making everything optional. Use
?only for values that can really be missing.
Key points
Type?holds a value ornil; you must unwrap it before use.if let xandguard let xunwrap safely;guard letkeeps the value for the rest of the function.??supplies a default value;?.chains calls and stops at the firstnil.!force unwraps and crashes onnil; reserve it for real programmer errors.mapandflatMaptransform optionals without unwrapping by hand.Optionalis an enum with cases.some(value)and.none.
Exercise
Write a function parseMarks(_ text: String?) -> Int? that returns nil if the text is nil, is not a number, or is outside 0 to 100, and the number otherwise. Test it with "85", "105", "abc" and nil, printing each result with ?? so that invalid input shows "invalid". Hint: String(value) turns an Int into text for the ??.
Show solution
func parseMarks(_ text: String?) -> Int? {
guard let text, let marks = Int(text), (0...100).contains(marks) else {
return nil
}
return marks
}
let inputs: [String?] = ["85", "105", "abc", nil]
for input in inputs {
let result = parseMarks(input).map { String($0) } ?? "invalid"
print(result)
}85 invalid invalid invalid
One guard unwraps the text, converts it, and checks the range. map { String($0) } turns a valid Int? into a String?, so ?? can supply the text "invalid".
Quick Quiz
1. What does print(Int("42")) print?
Int(String) returns an optional, and printing an optional shows the Optional(...) wrapper.
2. What is the type of name?.count when name is String??
Optional chaining always produces an optional, because the chain may stop at nil.
3. What happens when you force unwrap nil with !?
Force unwrapping nil is a fatal runtime error.
4. Which statement keeps an unwrapped value available for the rest of the function?
After guard let x else { return }, x is available until the end of the scope.
Learn Swift with a mentor
Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
