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.
importFoundation
classDataManager {
lazyvardata: [Int]= {
print("Loading data...")
returnArray(1...5)
}()
}
You can use the same pattern for other common cases:
importFoundation
classPerson {
letfirstName:String
letlastName:String
lazyvarfullName:String= {
"\(self.firstName) \(self.lastName)"
}()
lazyvardateFormatter:DateFormatter= {
letformatter=DateFormatter()
formatter.dateStyle= .medium
formatter.timeStyle= .short
returnformatter
}()
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
classNumbers {
lazyvarvalues: [Int]= {
Array(1...10_000)
}()
}
letnumbers=Numbers()
print(numbers.values.count)
Example 2: Build a value that needs self
Swift
classGreeting {
letname:String
lazyvarmessage:String= {
"Hello, \(self.name)!"
}()
init(name:String) {
self.name=name
}
}
letgreeting=Greeting(name:"Alex")
print(greeting.message)
Example 3: Create an expensive formatter only if used
importFoundation
classLogger {
lazyvarformatter:DateFormatter= {
letf=DateFormatter()
f.dateStyle= .long
f.timeStyle= .short
returnf
}()
funclog(_message:String) {
letstamp=formatter.string(from:Date())
print("[\(stamp)] \(message)")
}
}
letlogger=Logger()
logger.log("Started")
Common mistakes and how to fix them
Mistake 1: Using lazy with let
What you might do:
classExample {
lazyletvalue:Int=10
}
Why it breaks: Lazy properties must be var because Swift assigns them after initialization.
Correct approach:
classExample {
lazyvarvalue:Int=10
}
Mistake 2: Expecting the lazy initializer to run on every access
What you might do:
classCounter {
varcount=0
lazyvarnext:Int= {
count+=1
returncount
}()
}
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.
classCounter {
varcount=0
varnext:Int {
count+=1
returncount
}
}
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