Proxy Pattern
The problem
Sometimes you can’t or shouldn’t hand a caller a direct reference to a real object. The object might be expensive to create and shouldn’t be instantiated until someone actually needs it. It might live in another process or on a remote server. It might require permission checks before any method can run. Or it might benefit from caching so that repeated calls don’t repeat expensive work.
In every case, the answer is the same: put a surrogate in front of the real object. The surrogate implements the same interface, so callers don’t need to change. The surrogate intercepts calls, does its cross-cutting work (lazy init, auth, caching, serialization), and delegates to the real object when appropriate. Four variants cover most situations: virtual (lazy load), protection (permissions), caching, and remote.
Structure
classDiagram class Image { <<interface>> +display() } class RealImage { -filename: string -data: string +loadFromDisk() +display() } class ImageProxy { -filename: string -realImage: RealImage|null +display() } Image <|-- RealImage Image <|-- ImageProxy ImageProxy --> RealImage : creates on first display()When to use
- Construction of the real object is expensive and should be deferred until the object is actually used (virtual proxy).
- Access to the real object must be gated by permission or role checks (protection proxy).
- Results from the real object can be cached to avoid repeated expensive calls (caching proxy).
- The real object lives in another process, machine, or service and the caller needs a local stand-in (remote proxy).
Implementation
The examples demonstrate a virtual proxy that defers loading an image from disk until display() is first called, caching the result for subsequent calls. The TypeScript tab also includes a protection proxy that gates deleteUser behind a role check, showing the same interface from two proxy variants. Python uses abc.ABC with @abstractmethod for the Image interface. Go uses an interface type and a blank compile-time assignment (var _ Image = (*ImageProxy)(nil)) to confirm satisfaction without any runtime cost.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
The four proxy types
| Proxy type | What it does | Common use |
|---|---|---|
| Virtual | Defers creation of expensive object | Lazy-load images, database connections |
| Protection | Checks permissions before delegating | Role-based access control, API auth |
| Caching | Returns cached result on repeat calls | HTTP caching, memoization layers |
| Remote | Represents an object in another process | gRPC stubs, RMI, SOAP client proxies |
Tradeoffs
| Pro | Con |
|---|---|
| Transparent to the caller (same interface) | Adds a layer; debugging steps through two objects |
| Lazy init, caching, access control without touching the real object | Can hide errors if the proxy swallows exceptions |
| Testable in isolation: proxy can be tested without the real object | Caching proxy introduces stale-data risk |
| Easy to layer multiple proxies | Overusing proxies creates indirection soup |
Gotchas
- A protection proxy that logs but never blocks is audit logging, not access control. Be explicit about which one you are building.
- Virtual proxies create the real object on first use. If construction can fail, handle the error at that point rather than silently storing a partially initialized object.
- In Python,
__getattr__lets you build a generic proxy that forwards everything, but it bypasses type checking. Prefer an explicit interface when the proxy is part of a public API. - In Go, the proxy must implement the same interface as the real object. A blank assignment (
var _ Image = (*ImageProxy)(nil)) confirms this at compile time with no runtime cost. - Layering a caching proxy on top of a mutable real object can serve stale data. Define an explicit invalidation strategy before deploying a caching proxy in production.
References
- Design Patterns: Elements of Reusable Object-Oriented Software, the original GoF entry for Proxy (p. 207)
- Proxy pattern, Refactoring.Guru, illustrated walkthrough covering all four proxy types
- SourceMaking: Proxy, discussion of virtual, protection, and remote variants
- Head First Design Patterns, Freeman & Robson, chapter 11 covers Proxy with a remote proxy and a virtual proxy example side by side
Related topics
- Design Patterns, the full GoF catalog
- Decorator, also wraps an object behind the same interface, but to add behavior rather than to control access
- Facade, simplifies a subsystem rather than controlling access to a single object
- Flyweight, another structural pattern concerned with object overhead, but via sharing rather than interception