Last Updated: February 22, 2026
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 systems, the objects you interact with are often resource-heavy, remote, or sensitive. Think of database connections, third-party APIs, file systems, or large in-memory datasets. Direct access to these objects is not always ideal.
A proxy helps when you want to:
The proxy sits between the client and the real object, intercepting calls and deciding whether to forward them as-is, block them, or wrap them with extra 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 scroll through a list of thumbnails, and when they click on one, the full high-resolution image is displayed.
The straightforward approach creates all images upfront. Here is what the code looks like without any proxy:
Every image gets loaded during construction. Here is the sequence of what happens when the gallery starts:
Every HighResolutionImage loads its image data at the time of construction, even if the user never views the image. This leads to slow application startup, unnecessary memory consumption, and wasted I/O bandwidth. If your gallery displays dozens or hundreds of thumbnails, this approach quickly becomes a bottleneck.
What if you want to log every time an image is actually displayed? Add permission checks before loading a sensitive image? Cache previously loaded images for reuse?
Right now, you would have to modify the HighResolutionImage class directly, mixing responsibilities and breaking the Single Responsibility Principle.
The HighResolutionImage class does two things: it manages image data and it controls when that data is loaded. These are separate concerns. Loading policy (eager vs lazy, cached vs fresh, permitted vs denied) should not be baked into the data class itself.
We need a solution that allows us to:
HighResolutionImage class.This is where the Proxy Design Pattern comes into play.
The Proxy pattern is a structural pattern that provides a surrogate or placeholder for another object to control access to it. Instead of the client interacting directly with the real object, it interacts with a proxy that implements the same interface. The proxy decides when and how to forward requests to the real object.
Two characteristics define the pattern:
Proxy has four participants.
Image)The common interface that both the real object and the proxy implement.
In our image gallery example, Image is the Subject. It declares display() and getFileName() methods that every participant implements.
HighResolutionImage)The actual object that performs the real, expensive work.
In our example, HighResolutionImage loads a 10MB image from disk during construction. It is the object we want to defer creating until absolutely necessary.
ImageProxy)A lightweight stand-in that implements the Subject interface and controls access to the RealSubject.
In our example, ImageProxy stores the filename but does not create a HighResolutionImage until display() is called. It can answer getFileName() without loading anything.
ImageGalleryApp)The consumer that works with objects through the Subject interface.
In our example, the gallery app creates Image references and calls display() on whichever images the user clicks. It does not know or care whether those references point to proxies or real images.
Depending on the use case, the Proxy may take different forms:
Here is the Proxy workflow for our image gallery, step by step:
The client creates an ImageProxy with the filename "photo1.jpg". The proxy stores the filename but does not load the image. Construction is instant and uses almost no memory.
The real HighResolutionImage does not exist yet. No disk I/O, no memory allocation. The proxy is a lightweight placeholder.
The client calls getFileName() on the proxy. The proxy returns the stored filename directly, without creating the real image. Some operations do not need the real object at all.
The client calls display(). The proxy checks if the real image has been created. It has not, so the proxy creates a HighResolutionImage, which loads the file from disk and allocates memory.
After creating the real image, the proxy calls display() on it. The real image renders itself.
The client calls display() again. The proxy sees that the real image already exists and delegates directly, with no loading delay.
Now let's refactor our image gallery to use the Proxy pattern. Instead of eagerly loading every HighResolutionImage, we will use a proxy that wraps it and defers loading until the image is actually needed.
The ImageProxy implements the same Image interface as HighResolutionImage, so the client can use it interchangeably. Internally, it stores the filename and only creates the real image when display() is called.
From the client's perspective, nothing changes. It still interacts with Image references. But now, instead of dealing with heavyweight objects upfront, it gets lightweight proxies that only load the real object on demand.
Image references throughout, unaware of the proxyHighResolutionImage was not modified at alldisplay() call on photo1.jpg reuses the already-loaded image with no delayOne of the most powerful aspects of the Proxy pattern is how easily it extends to support different concerns. The virtual proxy we just built defers creation. But the same structure supports access control, logging, caching, and more, all without modifying the real object or changing the client code significantly.
A protection proxy controls access based on authorization rules. For example, only users with an ADMIN role should be able to view confidential images.
The key design decision here is how to pass user context. You might be tempted to change the display() method signature to accept a user role, but that would break the Image interface contract. Instead, the user context is passed through the proxy's constructor. The proxy knows who the user is, and the interface stays clean.
Notice that the Image interface is unchanged. The proxy constructor takes the user role as extra context, and display() checks access before creating or delegating to the real object. An admin can view anything. A regular user gets blocked from files containing "secret" in the name.
A logging proxy intercepts method calls and records them for auditing, debugging, or usage analytics. It wraps an existing Image and logs timestamps before and after each operation.
Notice that the logging proxy wraps any Image, not just HighResolutionImage. You could stack it on top of the virtual proxy: new LoggingImageProxy(new ImageProxy("photo1.jpg")). The logging proxy logs the call, then delegates to the virtual proxy, which handles lazy loading. Each proxy handles one concern.
To show the Proxy pattern in a completely different domain, let's build a database query caching system. The interface defines a simple query operation. The real implementation simulates a slow database. The caching proxy stores results and returns cached values for repeated queries.
The first query takes a full 100 milliseconds (simulated database latency). The identical second query returns instantly from cache. After clearing the cache, the same query hits the database again. The client code uses the same DatabaseService interface throughout, unaware that caching is happening behind the scenes.