CacheU
Low Level Design

Proxy Design Pattern

A detailed guide to the Proxy pattern, including direct vs indirect access, virtual proxy, protection proxy, remote proxy, structure, benefits, drawbacks, real-world examples, and implementations in C++, Java, and Python.


Proxy Design Pattern

The Proxy Design Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it.

In simple words:

A proxy stands between the client and the real object.

The client talks to the proxy. The proxy then talks to the real object.

This gives us a place to add:

  • security checks
  • lazy loading
  • network handling
  • caching
  • logging
  • validation

without changing the real object itself.


Introduction: What is a Proxy?

Imagine a simple scenario where a user needs to access a resource.

Without a proxy:

User → Resource

With a proxy:

User → Proxy → Resource

The proxy acts as an intermediary.

It behaves like the real object from the client’s point of view, but internally it can add extra logic before or after forwarding the request.


Why proxy is useful

A proxy helps when you want to:

  • control access to an object
  • delay expensive object creation
  • represent a remote object locally
  • validate requests before execution
  • cache results
  • monitor access

Core idea

The main idea is very simple:

The client should not always interact directly with the real object.

Instead, it should interact with a proxy that can:

  • decide whether access is allowed
  • create the real object when needed
  • forward the request to the real object
  • hide communication details

Formal definition

The Proxy pattern provides a surrogate or placeholder for another object to control access to it.


Main participants

RoleMeaningExample
ClientThe code that needs accessUser application
ProxyThe intermediary objectAccess controller / lazy loader
Real SubjectThe actual object doing the workFile, service, document, image

Proxy structure

Diagram
classDiagram class Subject { request } class RealSubject { request } class Proxy { realSubject request } class Client { use } Subject <|-- RealSubject Subject <|-- Proxy Proxy --> RealSubject Client --> Subject

Why use Proxy?

A proxy gives you control over access to the real object.

That control can be used for different purposes:

PurposeWhat proxy does
ProtectionChecks permissions before access
Virtual loadingCreates object only when needed
Remote accessHides network communication
CachingStores results for reuse
LoggingRecords access activity
ValidationChecks data before forwarding

The big idea

The client often cannot tell whether it is talking to the proxy or the real object.

That is because both usually implement the same interface.

This makes them interchangeable from the client’s perspective.


Direct interaction vs proxy interaction

Before proxy

Client → Real Object

With proxy

Client → Proxy → Real Object
Diagram
flowchart LR A[Client] --> B[Proxy] B --> C[Real Object]

Common characteristics of Proxy

A proxy usually has:

  • the same interface as the real object
  • a reference to the real object
  • extra logic before or after delegating
  • transparent usage from the client’s point of view

Main types of Proxy

The most common proxy types are:

  1. Virtual Proxy
  2. Protection Proxy
  3. Remote Proxy
Diagram
flowchart TD A[Proxy Pattern] --> B[Virtual Proxy] A --> C[Protection Proxy] A --> D[Remote Proxy]

1. Virtual Proxy

What it does

A Virtual Proxy delays the creation of an expensive object until it is actually needed.

This is also called lazy loading.


Why it matters

Sometimes creating an object is costly:

  • loading a large image
  • opening a huge file
  • initializing a heavy model
  • connecting to a complex resource

If the object is never used, creating it early is a waste.

A virtual proxy avoids that waste.


Example idea

Suppose an image viewer loads a large image from disk.

Without proxy:

  • the image is loaded immediately

With proxy:

  • a lightweight proxy is created first
  • the real image is loaded only when display() is called

Virtual proxy flow

Diagram
flowchart TD A[Client creates proxy] --> B[Proxy stores image path] B --> C{display called} C -- No --> D[Real object not created] C -- Yes --> E[Create real image] E --> F[Delegate display]

Virtual proxy structure

Diagram
classDiagram class Image { display } class RealImage { fileName display } class ImageProxy { fileName realImage display } Image <|-- RealImage Image <|-- ImageProxy ImageProxy --> RealImage

#include <iostream>
#include <string>
using namespace std;
 
class Image {
public:
    virtual void display() = 0;
    virtual ~Image() = default;
};
 
class RealImage : public Image {
private:
    string fileName;
 
public:
    RealImage(const string& name) : fileName(name) {
        cout << "Loading image from disk: " << fileName << endl;
    }
 
    void display() override {
        cout << "Displaying image: " << fileName << endl;
    }
};
 
class ImageProxy : public Image {
private:
    string fileName;
    RealImage* realImage;
 
public:
    ImageProxy(const string& name) : fileName(name), realImage(nullptr) {}
 
    void display() override {
        if (realImage == nullptr) {
            realImage = new RealImage(fileName);
        }
        realImage->display();
    }
 
    ~ImageProxy() {
        delete realImage;
    }
};
 
int main() {
    Image* image = new ImageProxy("photo.jpg");
    image->display();
    image->display();
    delete image;
    return 0;
}

Virtual proxy explanation

  • Image is the common interface
  • RealImage does the heavy work
  • ImageProxy delays creation
  • the first call creates the real object
  • later calls reuse it

2. Protection Proxy

What it does

A Protection Proxy checks whether the client has permission to access the resource.

It acts like a gatekeeper.


Why it matters

Sometimes an object is sensitive:

  • premium document
  • admin-only dashboard
  • confidential file
  • restricted API
  • paid feature

We do not want everyone to access it.

The proxy can check authorization before forwarding the request.


Protection proxy flow

Diagram
flowchart TD A[Client requests access] --> B[Proxy checks permission] B --> C{Allowed?} C -->|Yes| D[Forward to real object] C -->|No| E[Deny access]

Protection proxy structure

Diagram
classDiagram class Document { unlockPDF } class RealDocument { unlockPDF } class DocumentProxy { user realDocument unlockPDF } class User { isPremium } Document <|-- RealDocument Document <|-- DocumentProxy DocumentProxy --> RealDocument DocumentProxy --> User

#include <iostream>
#include <string>
using namespace std;
 
class User {
public:
    bool isPremium;
    User(bool premium) : isPremium(premium) {}
};
 
class Document {
public:
    virtual void unlockPDF() = 0;
    virtual ~Document() = default;
};
 
class RealDocument : public Document {
public:
    void unlockPDF() override {
        cout << "PDF unlocked successfully" << endl;
    }
};
 
class DocumentProxy : public Document {
private:
    User* user;
    RealDocument realDocument;
 
public:
    DocumentProxy(User* u) : user(u) {}
 
    void unlockPDF() override {
        if (user->isPremium) {
            realDocument.unlockPDF();
        } else {
            cout << "Access denied: premium required" << endl;
        }
    }
};
 
int main() {
    User basicUser(false);
    User premiumUser(true);
 
    DocumentProxy proxy1(&basicUser);
    DocumentProxy proxy2(&premiumUser);
 
    proxy1.unlockPDF();
    proxy2.unlockPDF();
 
    return 0;
}

3. Remote Proxy

What it does

A Remote Proxy represents an object that lives on another machine or in another address space.

It hides the network complexity from the client.


Why it matters

Without a remote proxy, the client would need to manage:

  • IP addresses
  • serialization
  • socket calls
  • connection setup
  • retries
  • remote communication details

A remote proxy makes the remote service feel local.


Remote proxy flow

Diagram
flowchart TD A[Client] --> B[Local Proxy] B --> C[Network Call] C --> D[Remote Server] D --> E[Response Returned] E --> B B --> A

Remote proxy structure

Diagram
classDiagram class DataService { fetchData } class RemoteDataService { fetchData } class DataServiceProxy { remoteConnection fetchData } DataService <|-- RemoteDataService DataService <|-- DataServiceProxy DataServiceProxy --> RemoteDataService

Remote proxy example in C++

#include <iostream>
#include <string>
using namespace std;
 
class DataService {
public:
    virtual string fetchData() = 0;
    virtual ~DataService() = default;
};
 
class RemoteDataService : public DataService {
public:
    string fetchData() override {
        return "Data from remote server";
    }
};
 
class DataServiceProxy : public DataService {
private:
    RemoteDataService* remoteService;
 
public:
    DataServiceProxy() : remoteService(nullptr) {}
 
    string fetchData() override {
        if (remoteService == nullptr) {
            cout << "Connecting to remote server..." << endl;
            remoteService = new RemoteDataService();
        }
        return remoteService->fetchData();
    }
 
    ~DataServiceProxy() {
        delete remoteService;
    }
};
 
int main() {
    DataService* service = new DataServiceProxy();
    cout << service->fetchData() << endl;
    cout << service->fetchData() << endl;
    delete service;
    return 0;
}

Unifying structure

All proxy types share the same core design:

  • proxy and real subject implement the same interface
  • proxy holds a reference to the real subject
  • client talks to the proxy as if it were the real object

This gives us:

  • interchangeability
  • control
  • transparency
  • separation of concerns
Diagram
classDiagram class Subject { operation } class RealSubject { operation } class Proxy { realSubject operation } Subject <|-- RealSubject Subject <|-- Proxy Proxy --> RealSubject

Is-A and Has-A relationships

Proxy uses both:

Is-A

The proxy is a type of the same interface as the real object.

Has-A

The proxy has a reference to the real object.

This combination allows the proxy to act as a substitute while still delegating work.


Benefits of Proxy Pattern

BenefitDescription
Access controlProtects sensitive resources
Lazy loadingDelays expensive object creation
Remote accessHides network communication
ValidationChecks input before forwarding
CachingImproves performance by reusing results
LoggingAdds tracking without changing real object

Drawbacks of Proxy Pattern

DrawbackDescription
Extra indirectionOne more layer to trace
More classesOften requires additional wrapper classes
Slight overheadProxy calls add a small cost
ComplexityCan be unnecessary for simple use cases

Proxy vs Decorator

These two patterns can look similar because both wrap objects.

PatternPurpose
ProxyControl access to an object
DecoratorAdd behavior to an object

Simple distinction

  • Proxy answers: “Should I allow access?”
  • Decorator answers: “How can I enhance behavior?”

Proxy vs Adapter

PatternPurpose
ProxyControls access
AdapterConverts one interface into another

Proxy keeps the same interface. Adapter changes the interface shape.


Proxy vs Facade

PatternPurpose
ProxyStand-in for access control
FacadeSimplified front door to a subsystem

Proxy represents a single object. Facade simplifies many objects.


Common real-world examples

Use caseProxy role
Large image loadingVirtual proxy
Premium document accessProtection proxy
Remote API accessRemote proxy
Caching expensive dataCaching proxy
Request auditingLogging proxy

When to use Proxy

Use Proxy when you want to:

  • defer heavy object creation
  • block unauthorized access
  • wrap remote services
  • add validation or caching
  • isolate expensive or sensitive operations

When not to use Proxy

Avoid using Proxy when:

  • the object is simple to create
  • there is no need for access control
  • the added indirection would only make the design harder
  • direct access is already clean and safe

Summary

The Proxy Pattern provides a representative object that controls access to another object.

It is useful for:

  • lazy loading
  • access control
  • remote communication
  • validation
  • caching
  • logging

The proxy and real object share the same interface, so the client can use them interchangeably.


Final takeaway

The Proxy Pattern is about one powerful idea:

Put a controllable representative in front of a real object.

That representative can:

  • delay creation
  • protect access
  • hide network complexity
  • add logic without changing the real object

It is a very practical pattern for building systems that need control, flexibility, and clean separation of responsibilities.