When developing for iOS, you end up using lazy var very often.
I did too. I habitually used it to defer heavy object initialization.
Then I wondered: isn’t lazy essentially the same Proxy Pattern from design-pattern textbooks?
Let me give you the conclusion first.
lazyis the language-integrated version of a Virtual Proxy that defers creation until the real object is needed.
Today, I’ll explain what Swift’s Proxy Pattern is and how the concept hides behind the lazy keyword, following what I learned by working through the code myself.
Here’s the key summary first.
- The Proxy Pattern is a structural pattern that controls access by placing a proxy in front of the real object.
lazyserves the same purpose as a Virtual Proxy that defers creation.- However,
lazycannot handle features such as access control or logging; it only performs lazy creation. - If you need logging or permission checks, you must create a proxy object yourself.
What exactly is Swift’s Proxy Pattern?
Proxy means a representative or intermediary.
You place an intermediary with the same interface in front of the object that does the actual work.
From the outside, it looks as though you are calling the real object, but the request actually goes through the proxy.
The proxy may simply forward the request or do additional work in between: checking permissions, recording calls, or deferring creation of the real object.
There are three typical uses.
- Virtual Proxy: defer heavy-object creation until it is actually needed
- Protection Proxy: block unauthorized access
- Logging Proxy: record call history
Today’s focus is the first one, the Virtual Proxy. It is exactly where lazy connects.
The identity of the proxy object hidden behind lazy
Let’s consider a situation involving high-resolution images.
Image loading is expensive. Loading everything in advance when it is not even visible wastes memory.
That leads to the idea of loading it only when it is truly needed. That is the core of a Virtual Proxy.
Here is a direct implementation using the Proxy Pattern.
protocol Image { func display() }
// Proxy: defer creating the real object until needed
final class ImageProxy: Image {
private let filename: String
private var real: RealImage? // Not created yet
init(_ filename: String) { self.filename = filename }
func display() {
if real == nil { real = RealImage(filename) } // Created at this moment
real?.display()
}
}
Only when display() is first accessed is RealImage created.
Until then, ImageProxy quietly waits while holding only the file name. That is exactly the proxy’s job.
Swift, however, has this pattern built into its syntax. It is lazy.
final class Gallery {
// created exactly once when this property is first accessed
lazy var cover: RealImage = RealImage("cover.png")
}
let g = Gallery() // not yet RealImage created
g.cover.display() // finally created here
When Gallery() is created, RealImage does not exist.
It is created the first time g.cover is accessed. The compiler effectively creates the deferred initialization that ImageProxy performed above.
If lazy exists, do we still need the Proxy Pattern?
That was my first question too.
The answer is no.
lazy handles only lazy creation.
It cannot block access, record calls, or return different objects depending on conditions.
I summarized the difference in a table.
| Item | lazy property | Custom proxy |
|---|---|---|
| Lazy creation | Yes | Yes |
| Access permission checks | No | Yes |
| Call logging and caching | No | Yes |
| Code size | One line | One class |
| Reusability | Limited to that property | Reusable in multiple places |
So the guideline is simple.
If you only need to defer creation, lazy one line is the answer. There is no need to create a class.
But if you need to intervene with permission checks, logging, or wrapping a remote call, create a proxy object yourself.
There is one point to watch out for.
lazy does not guarantee thread safety.
If multiple threads access it for the first time simultaneously, initialization may happen twice. In a multithreaded environment, it is safer to wrap this in a custom proxy and handle synchronization yourself.
Frequently asked questions
Q. How does lazy differ from a computed property?
lazy is evaluated once and its value is stored. A computed property is recalculated every time it is accessed. If you want heavy initialization to happen only once, use lazy.
Q. Why can lazy only be declared with var?
Because initialization happens later, an instance briefly exists before its value is determined. let does not allow that, so lazy let is syntactically impossible.
In summary, lazy is like a gift in which the language has packaged a Virtual Proxy from the Proxy Pattern in advance.
You do not need to create a proxy class for something that fits on one line, but understanding the concept hidden behind that line changes how you read code.
From today on, whenever you use lazy var, think, “Ah, I’m placing a proxy here.” The pattern will feel much more familiar.

