Cruxly › Low-level design › More Design Patterns
Proxy
intercepting access without the caller knowing
Open the interactive lessonForcing problem: callers need to interact with an object through its normal interface, but something has to happen around that access — deferring expensive construction, checking a permission, or standing in for an object that lives somewhere else — without callers writing extra code to trigger it.
The core mechanism: same interface, different behind it
interface Image { void render(); }
class RealImage implements Image {
private final String path;
RealImage(String path) {
this.path = path;
loadFromDisk(path); // expensive — happens at construction
}
public void render() { display(); }
}
Any of the three uses below wraps RealImage behind the same Image interface, so callers write identical code regardless of which is present — image.render() — which is the entire point: the interception is invisible from the call site.
Virtual proxy: defer expensive work until actually needed
class LazyImageProxy implements Image {
private final String path;
private RealImage real; // null until first render
LazyImageProxy(String path) { this.path = path; } // cheap — no disk I/O yet
public void render() {
if (real == null) real = new RealImage(path); // load on first real use
real.render();
}
}
A gallery holding a thousand LazyImageProxy instances pays no disk I/O until each image is actually scrolled into view — this is the identical lazy-construction idea as the Singleton holder pattern elsewhere in this track, applied per-object instead of once globally.
Protection proxy: enforce a check on every access
class ProtectedImage implements Image {
private final RealImage real;
private final User currentUser;
public void render() {
if (!currentUser.hasPermission("VIEW_IMAGES"))
throw new AccessDeniedException();
real.render();
}
}
The permission check happens on every call, transparently, without the caller writing an if statement at each of potentially dozens of call sites — the same "don't repeat the same check everywhere" motivation as Facade, applied to authorization instead of sequencing.
Remote proxy: the mechanism behind every RPC client you have ever used
This is worth naming explicitly if an interviewer pushes on "how would a client call a service running on another machine" — the generated client stub in gRPC, Java RMI, or any RPC framework is a remote proxy, and knowing the pattern's name for what the generated code is doing is a stronger answer than describing it as "magic."
The cost, and when Proxy is over-engineering
Every proxy adds one layer of indirection, and on a genuinely hot path that indirection is a measurable cost — a virtual method call through a proxy, times millions of invocations per second, is not free. Reserve Proxy for boundaries that already have a real cost to manage (disk I/O, network calls, security checks) rather than applying it to plain in-memory method calls "for flexibility."
A caching proxy and a decorator can look identical in code and mean different things. Decorator (covered elsewhere in this track) is about composing optional behaviour the caller explicitly chose to add. Proxy is about controlling access to an object transparently, without the caller opting in — the caller often cannot tell a proxy is present at all, which is precisely the point.
:::warn Proxy is not a reason to skip designing the real interface A common mistake is wrapping a poorly-designed interface in a proxy instead of fixing the interface. If callers need a proxy to make an interface usable, the interface itself likely has an Interface Segregation problem underneath, covered elsewhere in this track — proxy the access pattern, not a design flaw that should be fixed directly. :::
How to say it in the interview
"The tell is needing to do something before or after every access to an object — check a permission, defer loading, cross a network boundary — without changing what callers write. Proxy is the same interface, standing in front of the real thing."
Checklist
- Kept the proxy behind the same interface as the real object
- Used a virtual proxy to defer expensive construction until first real use
- Used a protection proxy to enforce a check once, transparently, on every access
- Named the remote proxy as the mechanism behind RPC client stubs specifically
- Reserved Proxy for boundaries with a real cost (I/O, network, security), not hot in-memory calls
- Distinguished Proxy (transparent access control) from Decorator (opted-in behaviour composition)
- Fixed a poorly segregated interface directly rather than wrapping it in a proxy