What are lazy vars in Swift?

Published on: April 23, 2024

Sometimes when you’re programming you have some properties that are pretty expensive to compute so you want to make sure that you don’t perform any work that you don’t absolutely must perform.

For example, you might have the following two criteria for your property:

  • The property should be computed once
  • The property should be computed only when I need it

If these two criteria sound like what you’re looking for, then lazy vars are for you.

A lazy variable is defined as follows:

class ExamResultsAnalyser {
  let allResults: [ExamResult]

  lazy var averageGrade: Float = {
    return allResults.reduce(0.0, { total, result in
      return total + result.grade
    }) / Float(allResults.count)
  }()

  init(allResults: [ExamResult]) {
    self.allResults = allResults
  }
}

Notice the syntax that's used to create our lazy var. The variable is defined as a var and not as a let because accessing the property mutates our object. Also notice that we're using a closure to initialize this property. This is not mandatory but it's by far the most common way I've initialized my lazy var properties so far. If you want to learn more about closures as an initialization mechanism, take a look at this post where I explore the topic in depth.

In this case, we’re trying to calculate an average grade based on some exam results. If we only need to do this for a handful of students this would be lightning fast but if we need to do this for a couple thousand students we’d want to postpone the calculation to the last possible second. And since an exam result is immutable, we don’t really want to recalculate the average every time we access the averageGrade property.

This is actually a key difference between computed properties and a lazy var. Both are used to compute something upon access, but a computed property performs its computation every time the property is accessed. A lazy var on the other hand only computes its value once; upon first access.

Note that accessing a lazy var counts as a mutating action on the enclosing object. So if you add a lazy var to a struct, the following code would not compile:

struct ExampleStruct {
  lazy var randomNumber = Int.random(in: 0..<100)
}

let myStruct = ExampleStruct()
myStruct.randomNumber

The compiler will show the following error:

Cannot use mutating getter on immutable value: 'myStruct' is a 'let' constant

And it will offer the following fix:

Change 'let' to 'var' to make it mutable

Because accessing the lazy var is a mutating operation, we must define our myStruct constant as a variable if we want to be able to access the randomNumber lazy var.

In Summary

All in all lazy var is an incredibly useful tool when you need to postpone initialization for a property to the last possible millisecond, and especially when it’s not guaranteed that you’ll need to access the property at all.

Note that a lazy var does not magically make the (expensive) computation that you’re doing faster. It simply allows you to not do any work until the work actually needs to be done. If you’re pretty sure that your lazy var will be accessed in a vast majority of cases it’s worth considering not making the property lazy at all; the work will need to be done at some point either way, and having less complexity is always a good thing in my book.

Subscribe to my newsletter