AlgoMaster Logo

Proxy Design Pattern

Ashish

Ashish Pratap Singh

7 min read

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:

  • Defer or control access to the actual implementation
  • Add extra functionality (e.g., logging, authentication) without modifying existing code.

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.

1. The Problem: Eager Loading

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.

1. The Image Interface

We define a basic Image interface to support rendering behavior:

2. High-Resolution Image Implementation

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.

What's Wrong With This Approach?

1. . Resource-Intensive Initialization

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
  • Wasted I/O bandwidth

If your gallery displays dozens or hundreds of thumbnails, this approach quickly becomes a bottleneck.

2. No Control Over Access

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'd have to modify the HighResolutionImage class directly — mixing responsibilities, breaking the Single Responsibility Principle, or worse, duplicating logic across clients.

What We Really Need

We need a solution that allows us to:

  • Defer the expensive loading of image data until it's actually needed.
  • Add extra behaviors like logging, access control, or caching without changing the existing HighResolutionImage class.
  • Maintain the same interface so that the client code doesn’t need to change.

This is where the Proxy Design Pattern comes into play.

2. What is the Proxy Pattern

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 initializationaccess controllogging, or caching without changing the original class or the client code.

Class Diagram

Subject (e.g., Image)

  • An interface or abstract class that defines the common operations shared by both the RealSubject and the Proxy.
  • This abstraction allows the client to treat the real object and its proxy interchangeably.

RealSubject (e.g., HighResolutionImage)

  • The actual object that performs the real work.
  • Typically expensive to create, resource-intensive to use, or requires additional layers of control.
  • The proxy represents this object and manages access to it.

Proxy (e.g., ImageProxy)

  • Implements the same interface as the RealSubject (i.e., Image), allowing it to stand in seamlessly.
  • Holds a reference to the real object and controls when and how it is created or accessed.
  • Acts as a gatekeeper, delegating calls to the real object only when appropriate.

Client (e.g., ImageGalleryApp)

  • Works with objects through the Subject interface.
  • Doesn’t need to know whether it’s interacting with the real object or a proxy.
  • This abstraction keeps the client code clean, decoupled, and adaptable.

Depending on the use case, the Proxy may take different forms:

  • Virtual Proxy: Defers creation of the real object until it’s actually needed (lazy loading).
  • Protection Proxy: Performs permission checks before allowing access to certain operations.
  • Remote Proxy: Handles communication between local and remote objects over a network.
  • Caching Proxy: Caches expensive results and avoids repeated calls to the real subject.
  • Smart Proxy: Adds logging, reference counting, or monitoring before/after method calls.

3. Implementing Proxy

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.

1. Create the Proxy Class

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.

2. Update the Client Code to Use the Proxy

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.

What Did We Gain?

Let’s recap the advantages of using the Proxy Pattern here:

  • Lazy Loading: Images are only loaded when the user views them, improving startup time and memory usage.
  • Clean Interface: The client code interacts with Image uniformly, unaware whether it’s dealing with a real object or a proxy.
  • No Code Changes to Real Object: The original HighResolutionImage remains untouched. We didn’t have to modify it to support lazy loading.
  • Reusability: 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.

4. Extending with Other Proxy Types

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:

  • Protection Proxy – restricts access based on user roles or permissions.
  • Logging Proxy – logs access attempts and usage metadata.

1. Adding a Protection Proxy

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.

2. Adding a Logging Proxy

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:

  • Acts as a stand-in for an expensive object (Virtual Proxy)
  • Delays initialization until needed (Lazy loading)
  • Restricts access to sensitive content (Protection Proxy)
  • Logs image access and usage patterns (Logging Proxy)

All of this happens transparently, without the client or the real object being aware.