StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsProxy

Proxy

A structural pattern where a stand-in with the real object's interface controls access to it, deferring, guarding, caching, or forwarding each call as policy demands.

Definition

An image object that loads 8 megabytes in its constructor charges everyone the full price at construction time, including the gallery that references 100 images to display three. Proxy is the Gang of Four structural pattern that puts a stand-in with the same interface in front of a real object to control access to it, what the catalog literature calls a wrapper or agent object standing in for something expensive or hard to reach.


A proxy is really just a gatekeeper wearing the subject's uniform. It implements the same interface as the real thing, holds or knows how to obtain it, and inserts one decision before each call: load now, check permission, serve from cache, marshal across the network, or just pass through. Control over access is the common thread, and which control you insert determines which of the classic proxy kinds you've built.

Paying for Pixels Nobody Sees

Here's the eager version. Image does its expensive work in the constructor, the gallery constructs three of them, and the user scrolls to exactly one. The class-level counter keeps the score honest:

Three loads for one view, and the waste scales with the gallery, not the user. The deeper problem is structural: the constructor is the only place doing the work, and constructors run when objects are referenced, not when they're used. No amount of cleverness at the call site fixes that, because the call site can't know the future either. The decision needs to move to the moment of use, which requires something standing between the caller and the cost.

The Stand-In at the Gate

The proxy version splits the class in two. RealImage keeps the expensive constructor, and ImageProxy implements the same Image interface while holding only a filename and an empty slot. The first display() call fills the slot, and every later call reuses it. Before reading past the code, predict the three printed counter values:

The counter reads 0 after building the whole gallery, then 1 after two display() calls on the same image: the first call paid the load, the second found the slot full and skipped straight to forwarding. The two unviewed images never load at all. And the gallery code holds plain Image references throughout, so nothing about the optimization leaked into the client.

Who Does What

Three roles. The Subject (Image) is the shared interface. The RealSubject (RealImage) does the actual work and carries the actual cost. The Proxy (ImageProxy) implements the Subject, manages access to the RealSubject, and decides when it comes into existence. The lazy-loading version here is the virtual proxy, one of four classic kinds alongside protection proxies that check permissions, remote proxies that hide a network hop, and caching proxies that reuse answers.


The pattern works like a celebrity's agent. Callers dial one number, the agent answers routine questions personally, decides which requests reach the celebrity, and the celebrity stays unbothered until genuinely needed. The analogy breaks on the most important property: you always know you're talking to an agent, while a proxy's entire design goal is being indistinguishable from the real thing through the interface. The moment callers can tell the difference, the pattern has failed at its one structural promise.

Proxy: Step-by-Step

Installing the pattern is four moves:

1. Share the Subject Interface

Proxy and real object both implement Image, which is what lets the proxy go anywhere the real thing goes. A client holding the interface can't tell which one it has, and the pattern depends on it staying that way.

2. Hold the Recipe, Not the Thing

ImageProxy stores a filename and a None, which is everything needed to build the real image later and nothing that costs memory now. Cheap construction is the virtual proxy's entire contribution.

3. Decide at the Gate

Every proxied method opens with a decision: create the real subject, reuse it, check a permission, serve a cached answer, or refuse. The decision is the proxy's job description, and what the decision is determines which kind of proxy you've built.

4. Stay Indistinguishable

After the gate, delegate faithfully: same arguments, same returns, same errors. A proxy that changes answers has started doing a decorator's job with a proxy's name, and callers deserve to know which one they're trusting.
The Go and Rust Versions

Go needs only what the code tab shows: a struct implementing the subject interface, gating each method, holding a nil pointer until first use. The interface satisfaction is structural, the laziness is a nil check, and the whole pattern fits in a screen of ordinary Go.


Rust spells laziness as Option<RealImage> and makes one demand worth hearing as advice: the gate method takes &mut self, because filling the slot is visibly a mutation. The temptation Rust warns against by name is faking transparency with Deref, making the wrapper auto-coerce into the inner type. The community's anti-pattern catalog is blunt that Deref is for pointer types, not for emulating inheritance, and a proxy that hides its delegation behind deref coercion surprises everyone who reads the call site. Delegate explicitly, method by method.

Proxy vs Decorator, the Identical Twins

You might think Proxy and Decorator are the same pattern photographed twice, and structurally they are: implement the subject's interface, hold a reference, forward calls. Intent is the entire difference, and two questions expose it. What does the wrapper do before forwarding: add behavior to the result, or decide whether and how the call gets through at all? And who created the inner object: a decorator is handed a finished component from outside, while a proxy typically creates or manages its real subject itself, the way ImageProxy minted its own RealImage.


The JDK ships a case the classification genuinely splits on: Collections.unmodifiableList returns a view that reads through to the live list and throws on every mutation. Reads forward untouched, writes are refused, and reasonable people file that as a protection proxy while others call it a decorator that removes capability. The debate is more useful than its answer, because it forces the real question: is refusing writes controlling access or changing behavior? Hold that question and you understand both patterns better than either label alone teaches.

When the Stand-Ins Take Over

Proxies fail by multiplying invisibly. Each cross-cutting concern gets its own layer, and soon a one-line call passes through authentication, caching, retry, metrics, and logging proxies before touching anything real, with no hint at the call site that any of it exists. Dynamic proxy machinery and aspect-oriented frameworks industrialize the problem: behavior gets injected by configuration, stack traces fill with generated class names, and the code a reader sees stops being the code that runs.


The defense is the same property that makes proxies good: indistinguishability is for callers, never for maintainers. Every proxy in the stack should be findable, named for its concern, and ordered on purpose, so the person debugging at 2am can list what stands between the call and the real subject without reading framework internals.

Best Practices

The shape is simple and the judgment calls are not, so these five carry the weight:


Virtual defers cost, protection checks permissions, remote hides a network, caching reuses answers. The mechanics overlap but the failure modes differ, and a class that won't say which it is usually grows into several at once.

Check Yourself

Two questions before you go. First: the gallery printed 0, then 1 after two display calls. Where did the 8 megabyte cost move, and what does the first caller experience that the second doesn't? Second: Proxy and Decorator share an identical structure, so name the two questions that tell you which one you're reading. Both answers are on this page.


Then go spot it in the wild. JavaScript built the pattern into the language: the ES6 Proxy object wraps a target with a handler whose traps intercept fundamental operations, which is this page's diagram shipped as a first-class value. ORM lazy loading, RPC client stubs, and API rate limiters are the same gate doing different jobs. Last, find a class in your codebase that checks, caches, or counts something before delegating to the object it wraps. That's a proxy, and saying which kind it is will make its next reviewer faster.

the_eager_constructor.py

class Image:
    load_count = 0  # class-wide tally for the demo

    def __init__(self, filename: str):
        self.filename = filename
        # The expensive part happens at construction, wanted or not
        Image.load_count += 1
        self.pixels = "<8 MB of pixels from " + filename + ">"

    def display(self) -> str:
        return "showing " + self.filename

# A gallery page references three images...
gallery = [Image("a.jpg"), Image("b.jpg"), Image("c.jpg")]

# ...but the user only ever scrolls to the first one
print(gallery[0].display())  # showing a.jpg
print(Image.load_count)      # 3: every image loaded, two for nothing

proxy_(virtual).py

from abc import ABC, abstractmethod

class Image(ABC):  # Subject: proxy and real image both speak this
    @abstractmethod
    def display(self) -> str: ...

class RealImage(Image):  # RealSubject: expensive to create
    load_count = 0

    def __init__(self, filename: str):
        self.filename = filename
        RealImage.load_count += 1
        self.pixels = "<8 MB of pixels from " + filename + ">"

    def display(self) -> str:
        return "showing " + self.filename

class ImageProxy(Image):  # Proxy: same interface, controls the loading
    def __init__(self, filename: str):
        self.filename = filename
        self._real: RealImage | None = None  # nothing loaded yet

    def display(self) -> str:
        # First call pays the load, every later call reuses it
        if self._real is None:
            self._real = RealImage(self.filename)
        return self._real.display()

# Example usage: the gallery builds instantly, no I/O at all
gallery = [ImageProxy("a.jpg"), ImageProxy("b.jpg"), ImageProxy("c.jpg")]
print(RealImage.load_count)  # 0: three proxies, zero loads

print(gallery[0].display())  # showing a.jpg
print(gallery[0].display())  # showing a.jpg (no second load)
print(RealImage.load_count)  # 1: only what was actually viewed