Skip to content
SP StackPractices
intermediate

Proxy Pattern

Provide a surrogate or placeholder for another object to control access to it. A structural design pattern for access control, lazy loading, and logging.

Topics: design

Proxy Pattern

Overview

The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object. The proxy controls access to the real subject, adding a layer of indirection that can be used for lazy loading, access control, caching, logging, or monitoring — without changing the subject’s code.

When to Use

Use the Proxy Pattern when:

  • You need lazy initialization for expensive objects (create on first use)
  • You want to control access rights to an object (authentication, authorization)
  • You need to cache results of expensive operations
  • You want to log or monitor calls to an object transparently
  • The real object is remote and you need a local representative

Solution

Python

from abc import ABC, abstractmethod

class Image(ABC):
    @abstractmethod
    def display(self):
        pass

class RealImage(Image):
    def __init__(self, filename: str):
        self.filename = filename
        self._load_from_disk()

    def _load_from_disk(self):
        print(f"Loading image: {self.filename}")

    def display(self):
        print(f"Displaying image: {self.filename}")

class ImageProxy(Image):
    def __init__(self, filename: str):
        self.filename = filename
        self._real_image = None

    def display(self):
        if self._real_image is None:
            self._real_image = RealImage(self.filename)
        self._real_image.display()

# Usage: expensive object is not loaded until needed
proxy = ImageProxy("photo.jpg")  # No loading yet
proxy.display()                   # Loads and displays
proxy.display()                   # Uses cached RealImage

JavaScript

class RealImage {
  constructor(filename) {
    this.filename = filename;
    this.loadFromDisk();
  }

  loadFromDisk() {
    console.log(`Loading image: ${this.filename}`);
  }

  display() {
    console.log(`Displaying image: ${this.filename}`);
  }
}

class ImageProxy {
  constructor(filename) {
    this.filename = filename;
    this.realImage = null;
  }

  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.filename);
    }
    this.realImage.display();
  }
}

// Usage
const proxy = new ImageProxy("photo.jpg");
proxy.display(); // Lazy loads
proxy.display(); // Uses cached instance

Java

public interface Image {
    void display();
}

public class RealImage implements Image {
    private final String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading image: " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

public class ImageProxy implements Image {
    private final String filename;
    private RealImage realImage;

    public ImageProxy(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

// Usage
Image proxy = new ImageProxy("photo.jpg");
proxy.display(); // Lazy loads
proxy.display(); // Reuses cached RealImage

Explanation

The Proxy Pattern involves three roles:

  • Subject Interface (Image): The common interface shared by both the real object and the proxy
  • Real Subject (RealImage): The actual object that performs the real work
  • Proxy (ImageProxy): Controls access to the real subject, adding behavior before or after forwarding requests

The proxy can intercept operations to add caching, logging, access control, or lazy initialization transparently.

Variants

VariantPurposeExample
Virtual ProxyLazy initializationLoading large images on demand
Protection ProxyAccess controlChecking permissions before method execution
Caching ProxyMemoizationCaching API responses or computed results
Remote ProxyNetwork transparencyLocal stub for a remote service
Smart ReferenceReference countingTracking object usage for cleanup

Best Practices

  • Keep the proxy interface identical to the real subject — clients should not know they are using a proxy
  • Use lazy initialization only when the real object is expensive — otherwise, the proxy adds unnecessary complexity
  • Handle thread safety in caching proxies when multiple clients may access shared cached data
  • Implement reference counting in smart proxies to manage lifecycle of expensive resources
  • Document proxy behavior (e.g., “this proxy caches results for 5 minutes”) so callers understand performance characteristics

Common Mistakes

  • Exposing the proxy’s internal state or letting clients bypass it to reach the real subject directly
  • Using a proxy when a simple decorator or direct reference would suffice, adding unnecessary indirection
  • Forgetting to handle exceptions in the proxy, letting failures silently bypass logging or cleanup logic
  • Implementing caching proxies without cache invalidation, leading to stale data
  • Not synchronizing access in multi-threaded environments, causing race conditions in lazy initialization

Frequently Asked Questions

Q: What is the difference between Proxy and Decorator? A: Both wrap objects and add behavior. Proxy controls access to the wrapped object (often for structural reasons like lazy loading or remote access). Decorator adds responsibilities dynamically, usually for functional enhancement. The intent differs even if the structure looks similar.

Q: Can a proxy wrap another proxy? A: Yes. You can stack proxies — for example, a caching proxy wrapping a remote proxy. Each layer adds its own behavior. Keep the stack shallow to avoid confusing stack traces and performance overhead.

Q: When should I use a Proxy instead of a Factory? A: Use a Factory when you want to control which class is instantiated. Use a Proxy when you want to control access to an already-instantiated object or delay its creation.