UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

The Complete Guide to Optionals in Swift

Unwrapping, nil coalescing, map(), flatMap(), and more.

Paul Hudson       @twostraws

Optionals are a fundamental part of Swift development, but at first they seem a bit confusing, and the more you learn the more you realize there’s a lot of subtlety to using them well.

In this article we’re going to look at a range of questions about optionals, starting off with easy questions that newcomers ask, then working our way forward to topics that will interest more experienced developers.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

What are optionals?

Optionals represent something that has no value. For example, consider this code:

let names = ["Margaret", "Matthew", "Megan"]
let position = names.index(of: "Mxyzptlk")

That creates an array of names and looks up the position of a name that doesn’t exist. What value should position have when that code runs?

  • It can’t hold 0 because that’s the position of Margaret.
  • It could hold -1 as a magic number, because that’s easy to remember but impossible number.
  • It could hold a very large number, like Int.max, which is harder to remember and also impossible – any such array of strings would take up too much memory!

None of those are good solutions, which is where optionals come in: they represent the absence of a value. An empty optional integer isn’t equal to 0, -1, 16777216, or any other number – it has nothing at all.

Optionals are created by appending ? to your type name. So, Int must always contain a real integer, but Int? might be an integer or might be missing, and String must always contain a real string, but String? might be a string or might be missing.

Optionals are best thought of as boxes that may or may not contain a value.

When are optionals useful?

Optionals let us represent missing, unknown, or deleted data. If you have a User struct with an age of 0, that might mean they have just been born or it might mean you don’t know – it’s hard to tell. By having making that same value optional it becomes clearer: when it’s 0 it’s a newborn baby, and when it’s nil you just don’t know the age.

What is unwrapping?

Because optionals may or may not be empty, Swift won’t let you us them freely. If an optional is empty – nil, in Swift – then it can’t be used in your code. For example:

  • If you have an optional string, you don’t want to try and show it to your users – the string might be empty.
  • If you have an optional number storing the amount of money in someone’s bank account, you don’t want to use it for mathematics – the number might be missing.
  • If you have an optional boolean that tracks whether someone agreed to your privacy policy, you don’t want to use it to decide whether or not to store analytics data – they might not have been asked to agree yet.

To keep you from using nil values by accident, Swift makes us unwrap the optionals: the process of looking inside to see whether there is a value there or not. If there is a value then Swift will unwrap it (take it out of the optional) and send it back to us as a non-optional, but if there isn’t then the test will fail.

The most common way to unwrap is using if let syntax like this:

let name: String? = "Taylor"

if let unwrappedName = name {
    print("Hello, \(unwrappedName)!")
} else {
    print("Hello, anonymous!")
}

That looks inside the value of name. If it has a value, it will be unwrapped (taken out of the optional) and placed into unwrappedName as a non-optional string, then the contents of the condition will be executed – the name will be printed. If there is no name inside, then “Hello, anonymous!” will be printed.

To be clear: name was an optional string, and unwrappedName is a non-optional string – it definitely has a value, even if that’s just the empty string "".]

The other common way to unwrap optionals is using guard let syntax, which is similar to if let except:

  1. If a guard unwrap fails you must exit the current scope. This usually means existing your method, but it might also mean exiting a condition or loop. Swift will refuse to compile unless you do this.
  2. If a guard unwrap succeeds, the unwrapped value exists after the guard block finishes. This is the opposite of if let, where the unwrapped value only exists inside the first code block.

Here’s how guard let looks:

func printName(_ name: String?) {
    guard let unwrappedName = name else {
        print("Hello, anonymous!")
        return
    }

    print("Hello, \(unwrappedName)!")
}

You will use both regularly.

What is force unwrapping?

As you’ve seen if let and guard let unwrap optionals safely: they look inside, run some code if the optional has a value, and run some code if the optional doesn’t have a value.

Force unwrapping skips that check and immediately extracts a value from optionals so you can use it immediately. It’s triggered by appending an exclamation mark after an optional variable, like this:

let name: String? = "Taylor"
let unwrappedName = name!

That might sound like a nice shortcut, and there are some times you’ll find it useful, but what would happen in this code:

let name: String? = nil
let unwrappedName = name!

That’s trying to unwrap an optional that doesn’t have a value. If you try to force unwrap an optional that is empty, your app will crash immediately. It will just die, with no way to recover. As a result, ! is sometimes called the crash operator.

Force unwraps are mainly used in two places:

  1. People are being lazy. I’m sorry that sounds hard, but it’s true: the overwhelming majority of force unwraps aren’t really safe, but are still use.
  2. When you know something the compiler doesn’t. You know for sure that the code in question is safe, but the API still gives you back an optional.

I can’t do much about the former other than encourage folks to think twice about force unwrapping, but the latter is more complex. Sometimes you can find ways of communicating your extra knowledge to the compiler so that it can check your work more carefully, but other times there is no graceful way and trying to work around optionals can just make your code harder to read.

For example, the window of an iOS app is optional, but if that’s set to nil then you’re done for because something is really broken. Or if you try to load a view controller from your storyboard and it can’t be found, that’s a fundamental error in your project – there’s no point trying to show users an empty view controller instead.

What is an implicitly unwrapped optional?

As you’ve seen, optionals might contain a value but might also be empty, and to avoid crashes and weird behaviors Swift makes you unwrap them using if let, guard let, or even force unwraps if you’re feeling brave.

Implicitly unwrapped optionals also might contain a value or be empty, but Swift won’t make you unwrap them before using them. That is, you can use them directly as if they weren’t optional at all. However, as with force unwrapping, if you try to use an implicitly unwrapped optional that happens to be empty, your app will crash immediately.

Implicitly unwrapped optionals are created by using ! in the type rather than ?. So, String must always have a real string, String? is an optional string (might have a value or might not, but Swift makes us unwrap it before use), and String! is an implicitly unwrapped optional string (might have a value or might not, but Swift won’t make us unwrap it before use.)

Implicitly unwrapped optionals are common in places where you know something the compiler doesn’t: that a specific object will start life as being empty, but afterwards will always have a value. It’s common with Interface Builder outlets, like these:

@IBOutlet var imageView: UIImageView!

When your view controller is initially created that image view will be nil because it hasn’t been made yet. But as soon as you try to show the view controller, its underlying view will be created and so will the image view - and will continue to exist until the view controller is finally destroyed.

Note: Implicitly unwrapped optionals are actually implemented as regular optionals under the hood, but they have a special flag that marks them as not needing to be unwrapped. This flag is attached specifically to your implicitly unwrapped optional, and gets lost as soon as you take a copy of it.

For example:

let name: String! = "Taylor"
let nameCopy = name

In that instance, name is an implicitly unwrapped optional (String!), but nameCopy is a regular optional (String?).

Where else are optionals made?

Apart from when you create them explicitly, optionals are created in three other ways: optional typecasts (as?), optional try (try?), and failable initializers (init?()). All three of these are common in Swift, so it’s worth taking the time to learn them thoroughly.

First, as? is used when you want to convert one type to another. For example, if Swift thinks you have an instance of UIViewController but you know you actually have an instance of MyCustomViewController, you might write this:

if let myVC = viewController as? MyCustomViewController {
    // we have a MyCustomViewController
} else {
    // we have something else
}

Many of UIKit’s APIs return these kinds of superclasses when really we know we should get something specific. For example, dequeuing a cell from a table always returns a UITableViewCell, but if you have a custom class you’ll want to typecast that using as?.

Second, try? is useful when you want to convert a throwing function into a non-throwing function that returns an optional. If your function call succeeds your optional will have the function’s return value inside, but if the function call throws an error then you’ll get back an optional containing nil.

For example, trying to create a string from the contents of a file is a throwing function, but if you just wanted to know whether it worked or not you could use try? like this:

if let saved = try? String(contentsOfFile: "saved.txt") {
    print(saved)
} else {
    print("No saved text.")
}

Finally, failable initializers create optional instances of a type, allowing the initializer to return nil if there was a problem.

For example, we could create a User struct that gets created with an integer ID number like this:

struct User {
    var id: Int
}

However, what if we wanted to enforce the rule that ID numbers must always be greater than zero? The struct above will get Swift’s automatic memberwise initializer, but it won’t validate the id number that is passed in.

To fix that we could write our own failable initializer, like this:

struct User {
    var id: Int

    init?(id: Int) {
        if id < 0 {
            return nil
        }

        self.id = id
    }
}

Creating a User will now always return an optional user, because our ID number might get rejected.

Note: as?, try?, and init?() all have force variants: as!, try!, and init!(). If you use as! and your typecast fails then your app crashes. If you use try! fails and your call throws an error, then – you guessed it! – your app crashes. If you use init!() you’ll just get back an implicitly unwrapped optional.

What is optional chaining?

Optional chaining allows us to work with several optionals at once. For example, if we had an optional user, which had an optional middleName property, we can uppercase it like this:

let upperMiddle = user?.middleName?.uppercased()

Here’s what will happen when that code is run:

  • If user is nil then upperMiddle will be set to nil immediately – the rest of the line is ignored.
  • If user has a value then Swift will look at its middleName property – if that is nil then upperMiddle will be set to nil immediately and the rest of the line ignored.
  • If both user and its middleName property have values, then the middle name will be uppercased and stored in upperMiddle.

Because the code can return nil at two different places, upperMiddle will end up being an optional string.

You can use optional chaining as much as you want in a single line of code: a?.b?.c?.d?.e?.f is fine, for example.

What are optional optionals?

Optional optionals are a good sign that you need to rethink your code a little. If an optional integer is like a box that might have a number or might not, then an optional optional integer is that box inside another box.

Optional optionals are written with two question marks, like this: Int?? or String??. And yes, before you ask, you can even make optional optional optionals, but honestly anything beyond a regular optional is a bad idea.

What is nil coalescing?

It’s common to want to check whether an optional has a value or not, but if it doesn’t provide a default instead. In Swift we do this with the nil coalescing operator, ??, which means “if the following optional has a value then unwrap it and send it back to me, otherwise use this other value instead.

As an example, here’s some code we wrote earlier that prints of two strings:

let name: String? = "Taylor"

if let unwrappedName = name {
    print("Hello, \(unwrappedName)!")
} else {
    print("Hello, anonymous!")
}

Using nil coalescing we could collapse that down to just two lines:

let unwrappedName = name ?? "anonymous"
print("Hello, \(unwrappedName)")

We could even put the nil coalescing into the string interpolation to make one line, like this:

print("Hello, \(name ?? "anonymous")")

You can use nil coalescing anywhere you have optionals, which includes as?, try?, init?(), and more. Again, here’s some code from earlier:

if let saved = try? String(contentsOfFile: "saved.txt") {
    print(saved)
} else {
    print("No saved text.")
}

With nil coalescing we could collapse it down to this:

let saved = (try? String(contentsOfFile: "saved.txt")) ?? "No saved text."
print(saved)

There’s a second form of the nil coalescing operator that lets us try multiple alternative values. For example, suppose you had a dictionary of values from a web server, describing someone’s address. Here’s part of it:

let address = [
    "postCode": "BA1 2BW"
]

You’re not quite sure what their postal code is, so you want to try a few options: “zip”, “zipCode”, and “postCode”. Using nil coalescing you can try them all like this:

let postCode = address["zip"] ?? address["zipCode"] ?? address["postCode"]

As soon as one of them is found, the rest of the line is ignored and the value is returned.

However, because all three might fail the value you get back will be an optional. If you want to get a non-optional back, all you need to do is make sure the last item in your chain always returns a value, like this:

let postCode = address["zip"] ?? address["zipCode"] ?? address["postCode", default: "Unknown"]

How can you check an optional is empty?

The simplest way of checking for empty optionals is to compare them against nil, like this:

var name: String? = "Frodo"

if name == nil {
    print("Sneaky little hobbitses!")
}

In switch blocks you can also use question marks to check for the presence of a value, while also unwrapping whatever was found. For example:

var name: String? = "Frodo"
var password: String? = "h0bbit0n"

switch (name, password) {
case let (name?, password?):
    print("Hello, \(name). Your password is \(password)")
case let (username?, nil):
    print("Please enter a password, \(username).")
default:
    print("Who are you?")
}

The same question mark syntax also works with arrays. If you use for case let someValue? in someArray then Swift will check each optional, ignore any that are nil, but unwrap any that aren’t nil and execute your loop body for that item.

For example:

let hobbits: [String?] = ["Frodo", nil, "Samwise", nil, "Meriadoc"]

for case let hobbit? in hobbits {
    print("\(hobbit) left Hobbiton.")
}

How are optionals implemented in Swift?

Under the hood, optionals are actually Swift enums that have two cases: .none for empty optionals, and .some(WrappedValue) if they do have a value.

Although there is some special compiler work around things like if let and guard let, a lot of the functionality of optionals themselves is actually implemented in Swift code – you can read the source code right here. It’s not very long, and I think you’ll learn a lot!

Here’s how optionals are defined:

public enum Optional<Wrapped>: ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
}

What protocols do optionals conform to?

You just saw how optionals were defined, and there’s a conformance right there for ExpressibleByNilLiteral so that we can make optionals that have an initial value of nil.

However, optionals also use conditional conformance to conform to both Equatable and Hashable but only if the type they contain conform to those protocols.

This means you can write code like this:

let age1: Int? = 30
let age2: Int? = 40

if age1 == age2 {
    print("Same.")
} else {
    print("Different.")
}

That compares two optionals directly, and works fine because:

  1. If both age1 and age2 are nil then the result is true: they are the same.
  2. If one of them is an integer and one is nil, then the result is false because they are not the same.
  3. If both of them are integers then Swift will do a regular integer comparison and return true or false as appropriate.

This functionality is automatically enabled through conditional conformances, so if you want to use it with your custom types make sure your type conforms to Equatable or Hashable.

How can you transform optionals?

Optionals have two methods that are helpful for transforming their contents: map() and flatMap(). Both of them have map() somewhere in the name, which a functional programming term that means “transform”: you provide them with a transformation closure that does something to your optional data.

Let’s take a look at map() first, because it’s easiest. You If you call map() on an optional that has a value, the value gets removed from its optional container, transformed using your closure, then put back into a new optional.

For example, here’s a getUserAge() method that returns an optional integer for times when we didn’t know their age:

func getUserAge() -> Int? {
    return 38
}

let age = getUserAge()

We can use map() to transform it into a string like this:

let ageString = age.map { "Your age is \($0)" }

Remember, if age was empty then the map() call just returns nil straight away, but if it has a value – like it does here – then it will be transformed into a string then rewrapped in a new optional. So, to use that optional we can still use nil coalescing, like this:

print(ageString ?? "We don't know your age.")

So, the complete code is this:

func getUserAge() -> Int? {
    return 38
}

let age = getUserAge()
let ageString = age.map { "Your age is \($0)" }
print(ageString ?? "We don't know your age.")

If we wanted to implement just the last three lines without map() we’d need to use the longer if let form, like this:

if let age = getUserAge() {
    print("Your age is \(age)")
} else {
    "We don't know your age."
}

So that’s map(): it transforms an optional’s value if it has one, or returns nil otherwise. But let’s look at an example where map() struggles:

let age: String? = "38"
let ageInt = age.map { Int($0) }

That creates an age value using an optional string, for example if the user typed a number into a text box. It then uses map() to transform it into integer. When that code runs, what data type is ageInt?

It’s not Int.

It’s not even Int.

It’s Int?? – an optional optional integer. Like I said earlier, optional optionals are a good sign that you need to rethink your code a little, because to use that you need to unwrap it twice.

This happens because map() wraps its return value in an optional, but our transformation closure creates an integer from a string, which is a failable initializer. So, the initializer creates an optional, and that gets wrapped in another optional by map().

This is where flatMap() comes in: if your transformation closure returns an optional, it gets collapsed into a single optional – either the whole thing exists or nothing exists. The code is almost identical:

let age: String? = "38"
let ageInt = age.flatMap { Int($0) }

Now ageInt is a regular optional integer, which is much more usable.

The rule for which to use is really simple: if your transform closure returns an optional use flatMap(), otherwise use map().

What did we learn?

Optionals are one of the most important safety features of Swift, making sure we never try to work with empty memory by accident.

With features like guard let, nil coalescing, map() and flatMap(), and, yes, even force unwrapping sometimes, we can make optionals much more pleasant to use, so we can focus on actually using the data they contain.

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.5/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.