How to Use Lazy Properties in Swift
What you’ll build or solve
You’ll add lazy stored properties to a type, so the expensive setup happens only when needed.
When this approach works best
Lazy properties work best when:
Learn Swift on Mimo
- A property is expensive to create, like a formatter, parser, or large dataset.
- The value depends on
self, so it cannot be created safely before initialization completes. - The property might never be used in some code paths, so delaying creation saves work.
This is a bad idea for cheap values or values that must always exist immediately. In those cases, a normal stored property is simpler.
Prerequisites
- Xcode or a Swift Playground
- Basic knowledge of stored properties and initialization in Swift
Step-by-step instructions
Step 1: Declare a lazy stored property
Add lazy before var, then provide a value. A closure initializer works well for setup that takes multiple lines.
Swift
import Foundation
class DataManager {
lazy var data: [Int] = {
print("Loading data...")
return Array(1...5)
}()
}
You can use the same pattern for other common cases:
Swift
import Foundation
class Person {
let firstName: String
let lastName: String
lazy var fullName: String = {
"\(self.firstName) \(self.lastName)"
}()
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter
}()
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
What to look for
lazyworks only withvar, notlet.- The closure runs once and Swift stores the result.
- Later reads reuse the stored value.
Examples you can copy
Example 1: Delay loading a dataset
Swift
class Numbers {
lazy var values: [Int] = {
Array(1...10_000)
}()
}
let numbers = Numbers()
print(numbers.values.count)
Example 2: Build a value that needs self
Swift
class Greeting {
let name: String
lazy var message: String = {
"Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
let greeting = Greeting(name: "Alex")
print(greeting.message)
Example 3: Create an expensive formatter only if used
Swift
import Foundation
class Logger {
lazy var formatter: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .long
f.timeStyle = .short
return f
}()
func log(_ message: String) {
let stamp = formatter.string(from: Date())
print("[\(stamp)] \(message)")
}
}
let logger = Logger()
logger.log("Started")
Common mistakes and how to fix them
Mistake 1: Using lazy with let
What you might do:
Swift
class Example {
lazy let value: Int = 10
}
Why it breaks: Lazy properties must be var because Swift assigns them after initialization.
Correct approach:
Swift
class Example {
lazy var value: Int = 10
}
Mistake 2: Expecting the lazy initializer to run on every access
What you might do:
Swift
class Counter {
var count = 0
lazy var next: Int = {
count += 1
return count
}()
}
Why it breaks: The closure runs once. After that, next returns the stored value.
Correct approach: Use a computed property when you need a fresh calculation each time.
Swift
class Counter {
var count = 0
var next: Int {
count += 1
return count
}
}
Troubleshooting
- If you see
lazy cannot be used on a let, changelettovar. - If the initializer never runs, confirm something actually reads the property.
- If you get an error about using
selftoo early, move the logic into a lazy closure. - If you expected recalculation, switch from
lazyto a computed property.
Quick recap
- Add
lazybeforevarto delay initialization. - Initialize it with a value or a closure that returns a value.
- Lazy initialization runs once and stores the result.
- Use
lazyfor expensive setup or values that depend onself. - Use computed properties when you need recalculation on every access.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot