Optionals
Swift's answer to "there might be nothing here" — and the reason iOS apps stopped crashing on null.
By the end you will be able to
- Explain what `String?` is and why it is a different type from `String`
- Unwrap safely with if let, guard let and ??, and say when each fits
- Recognise when force-unwrapping is defensible and when it is a latent crash
Int("42") converts a string to a number. What does Int("banana") give you?
The problem optionals solve
In Objective-C, Java, or JavaScript, any object reference can secretly be null. Nothing in the type says so. You find out at runtime, usually in production, usually on someone else's phone.
Swift's fix is not to remove the concept of "no value" — you genuinely need it. Its fix is to move that fact into the type system.
var name: String // always holds a String
var nick: String? // holds a String, or nothing
String? is shorthand for Optional<String>. It is a genuinely different type from String, and Swift will not let you use one where the other is expected. That single restriction is what eliminates the whole class of null crashes.
Getting the value out
An optional is a box. You cannot use what is in the box without opening it, and opening it means handling the case where it is empty. Swift gives you four tools.
1. if let — do something when there is a value
let input = "42"
if let number = Int(input) {
print("Twice that is \(number * 2)")
} else {
print("That was not a number")
}
Inside the if block, number is a plain Int — already unwrapped. Outside it, it does not exist.
Since Swift 5.7 you can drop the repetition when the names match:
if let input { // same as `if let input = input`
print(input.count)
}
2. guard let — bail out early when there is not
func describe(_ text: String?) -> String {
guard let text else {
return "nothing"
}
// `text` is a plain String for the rest of the function
return "got \(text.count) characters"
}
guard inverts the shape. if let nests the happy path inside braces; guard let handles the failure and leaves the happy path unindented for the rest of the function. In practice you will write guard far more often — it keeps functions flat.
The else block of a guard must leave the current scope: return, throw, break, or continue. That is not bureaucracy — imagine the else could fall through. The very next line says text.count, on a text that was never bound. The promise "text is a plain String for the rest of the function" only holds because the failure path provably cannot reach that code. The compiler isn't demanding an exit for its own sake; it is refusing to let the happy path run without the value it depends on.
3. ?? — supply a fallback
let displayName = nick ?? "Anonymous"
Read it as "or else". If the left side has a value, use it; otherwise use the right side. The result is non-optional, which is the point.
4. ! — force it, and crash if you are wrong
let number = Int("42")! // I am certain this is a number
This says: I know there is a value here; if I am wrong, crash the app. Sometimes that is genuinely correct — a value you just created, a resource compiled into the app. Far more often it is a latent crash written by someone in a hurry.
Three lines, one of which crashes. Which one, and what does the program print before it stops?
let raw = ["10", "20", "thirty"]
var total = 0
for text in raw {
print("parsing \(text)")
total += Int(text) ?? 0
}
print("total \(total)")Optional chaining
When you have an optional and want to reach through it, ?. does the work:
let length = nick?.count // Int? — nil if nick is nil
If nick is nil, the whole expression is nil and count is never called. If it has a value, you get its count — wrapped in an optional, because the expression as a whole can still produce nothing.
Chains can be long, and any nil anywhere short-circuits the rest:
let city = user?.address?.city?.uppercased()Optionals are just an enum
This is worth knowing because it demystifies the whole feature. Optional is an ordinary enum in the standard library:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
nil is .none. A value is .some(value). The ? syntax, if let, and ?? are all conveniences over that enum — which is why you can pattern-match on it directly:
switch nick {
case .some(let value): print("nick is \(value)")
case .none: print("no nick")
}
You will rarely write that. But knowing it is there is the difference between "optionals are magic" and "optionals are a type I could have written myself".
You have var scores: [String: Int] and write scores["Ada"] += 1. Why does this not compile?
Safe average
averageAge should return the mean age, or nil when the array is empty — because the average of no numbers is not a number, it is nothing.
Flatten a chain
Print each user's city in upper case, or UNKNOWN when they have no address. One line of output per user, in order.
Parse or explain
Write parseAges(_:) that takes [String] and returns the parsed numbers, skipping anything that is not a number. Then print how many were skipped.
A colleague says: "Optionals are just null with extra steps — I have to write more code to do the same thing." Write a short reply (four or five sentences) that takes the complaint seriously and explains what the extra steps actually buy.
You can now:
- Say why
String?is a different type fromString, and what that buys you - Unwrap with
if let,guard let,??and?., and pick the right one - State the rule for when
!is defensible - Explain that
Optionalis an ordinary enum with.noneand.some
Next up: control flow — and Swift's switch, which is far more capable than the one you know from C-family languages.