The Proxy Design Pattern is a structural pattern that provides a placeholder or surrogate for another object, allowing you to control access to it.
In real-world applications, you often work with resource-intensive, remote, or sensitive components — such as database connections, third-party APIs, file systems, or large in-memory datasets.
Sometimes you also want to:
A proxy sits between the client and the real object, intercepting calls and optionally altering the behavior.
Let’s walk through a real-world example and see how we can apply the Proxy Pattern to build safer, smarter, and more controlled interactions with expensive or sensitive resources.
Imagine you're building an image gallery application. Users can scroll through a list of image thumbnails, and when they click on one, the full high-resolution image is displayed.
We define a basic Image
interface to support rendering behavior:
This class represents an actual full-size image that is loaded and rendered. The moment it’s constructed, it loads the full image data — which is intentionally slow and memory-heavy.
Every HighResolutionImage
loads its image data at the time of construction, even if the user never views the image. This leads to:
If your gallery displays dozens or hundreds of thumbnails, this approach quickly becomes a bottleneck.
What if you want to:
Right now, you'd have to modify the HighResolutionImage
class directly — mixing responsibilities, breaking the Single Responsibility Principle, or worse, duplicating logic across clients.
We need a solution that allows us to:
HighResolutionImage
class.This is where the Proxy Design Pattern comes into play.
The Proxy Design Pattern provides a stand-in or placeholder for another object to control access to it. Instead of the client interacting directly with the “real” object (e.g.,
HighResolutionImage
), it interacts with a Proxy that implements the same interface.
This allows the proxy to perform additional responsibilities — such as lazy initialization, access control, logging, or caching without changing the original class or the client code.
Image
)HighResolutionImage
)ImageProxy
)RealSubject
(i.e., Image
), allowing it to stand in seamlessly.ImageGalleryApp
)Subject
interface.Depending on the use case, the Proxy may take different forms:
Now let’s refactor our image gallery to use the Proxy Design Pattern.
Instead of eagerly loading every HighResolutionImage
, we'll use a proxy that wraps it and defers loading until the image is actually needed.
Our ImageProxy
will implement the same Image
interface as HighResolutionImage
, allowing it to be used interchangeably by the client. Internally, it will hold a reference to the real image but only initialize it when required.
From the client’s perspective, nothing changes — it still interacts with an Image
. But now, instead of dealing with a heavyweight object upfront, it gets a lightweight proxy that only loads the real object on demand.
Let’s recap the advantages of using the Proxy Pattern here:
Image
uniformly, unaware whether it’s dealing with a real object or a proxy.HighResolutionImage
remains untouched. We didn’t have to modify it to support lazy loading.ImageProxy
can be reused for other optimizations like logging, caching, or access control later.This is the perfect use case for a Virtual Proxy — a proxy that stands in for a costly object and defers its creation until absolutely necessary.
By introducing just one small class (
ImageProxy
), we improved performance, saved memory, and kept our design clean, extensible, and easy to maintain — all without touching the original image class or altering client logic.
One of the most powerful aspects of the Proxy Pattern is how easily it can be extended to support different concerns — often simultaneously — without modifying the real object or changing client code significantly.
Let’s explore how we can evolve our ImageProxy
to act not only as a Virtual Proxy, but also as a:
A Protection Proxy controls access to sensitive operations based on authorization rules. For example, only users with an ADMIN
role should be able to view confidential images.
We can extend the display()
method in ImageProxy
to check access before loading or displaying the image:
This approach keeps the
HighResolutionImage
free of authorization logic while enabling fine-grained access control at the proxy level.
A Logging Proxy intercepts method calls and logs them before or after forwarding them to the real object. This is especially useful for auditing, debugging, or usage analytics.
Here’s how we can enhance the original display()
method:
With just a few extra lines of code, ImageProxy
now performs multiple roles:
All of this happens transparently, without the client or the real object being aware.