Design Patterns: Structural · Structural Patterns

Proxy Pattern

Standing in for a real object behind the same interface, controlling access to it.

  • 5 min read
  • 10 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the structural patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Proxy is the structural pattern for standing in front of an object and controlling access to it: the proxy has the same interface, so callers can't tell the difference, but it decides when the real object is created, whether a call is allowed, whether the call is needed at all, or where it really runs. Lazy loading in ORMs, caching clients, security checks and remote stubs are all proxies. This skill covers the kinds of proxy, how they work, and where they surprise people.

A photo gallery shows 500 thumbnails, but each HighResImage loads a 20 MB file in its constructor. Creating the list takes forever, even though the user will open maybe three photos.

A second, related problem: you want every call to a document store to be permission-checked and logged. Putting those checks inside every method of the store mixes security code with storage code - and it's easy to forget one.

We'll go from simple to more technical:

  1. The pattern: same interface, controlled access.
  2. The kinds of proxy - virtual, protection, caching, remote, logging.
  3. Where you'll see proxies: ORMs, Spring, RPC stubs, CDNs.
  4. The surprises: self-invocation, lazy loading after the session closes, stale caches and chatty remote calls.

The pattern

A Proxy implements the same interface as the real object and stands in for it, controlling access: when the real object is created, whether a call is allowed, or whether it's needed at all.

Java

interface Image {
    void display();
}

class HighResImage implements Image {
    HighResImage(String path) { /* expensive: loads 20 MB from disk */ }
    public void display() { /* draw it */ }
}

class LazyImageProxy implements Image {
    private final String path;
    private HighResImage real;          // not loaded yet

    LazyImageProxy(String path) { this.path = path; }   // cheap

    public void display() {
        if (real == null) real = new HighResImage(path);  // load on first real use
        real.display();
    }
}

Python

from abc import ABC, abstractmethod


class Image(ABC):
    @abstractmethod
    def display(self) -> None: ...


class HighResImage(Image):
    def __init__(self, path: str):
        ...  # expensive: loads 20 MB from disk

    def display(self) -> None:
        ...  # draw it


class LazyImageProxy(Image):
    def __init__(self, path: str):                 # cheap
        self._path = path
        self._real: HighResImage | None = None     # not loaded yet

    def display(self) -> None:
        if self._real is None:
            self._real = HighResImage(self._path)  # load on first real use
        self._real.display()

The gallery holds 500 cheap proxies; only the photos the user actually opens are ever loaded.

How it works, step by step

  1. Proxy and real object implement the same interface, so clients can't tell them apart.
  2. The proxy holds (or knows how to get) the real object.
  3. Before forwarding a call it applies its control: load lazily, check permissions, return a cached value, or send the call over the network.
  4. Common kinds: virtual (lazy loading), protection (access checks), caching, remote (stand-in for an object on another machine) and logging proxies.

Where you'll see it

  • Hibernate/JPA lazy associations: order.getCustomer() returns a proxy that loads the customer on first use.
  • Spring wraps your beans in proxies to add @Transactional, @Cacheable and security behavior.
  • java.lang.reflect.Proxy creates proxies at runtime; gRPC and RMI client stubs are remote proxies.
  • In Python, SQLAlchemy's lazy relationships load on first access, functools.cached_property computes an attribute on first use, and a class with __getattr__ can forward every call to a real object it creates - or connects to - only when needed.
  • In infrastructure, reverse proxies and CDNs cache and protect servers the same way.

Choosing among the structural patterns

If you need to...Reach forWhat it changes
Make an existing class fit the interface your code expectsAdapterChanges the interface; the behavior stays the same.
Add behavior to an object without changing its class or interfaceDecoratorSame interface, extra behavior, and wrappers can be stacked.
Give a complicated subsystem one simple front doorFacadeA new, simpler interface over many classes.
Treat a single item and a group of items the same wayCompositeA tree: groups contain items or other groups.
Control access to an object (lazy loading, caching, security, remote calls)Proxy (this lesson)Same interface, but it decides when and whether the real object is used.

Common mistakes

MistakeWhat happensDo this instead
Calling a proxied method on this / selfthe proxy's behavior (transactions, caching, checks) is skippedcall through the proxy, or move the method to another object
Touching a lazy ORM proxy after the session closesLazyInitializationException or a detached-instance errorload what you need inside the session
Lazy proxies in a loop over a listone query per item (N+1)fetch the related data in one query
A caching proxy with no invalidation planstale data served indefinitelya TTL, event-based invalidation, or both
Treating a remote proxy like a local objecthundreds of round trips, unhandled timeoutscoarse-grained calls and explicit failure handling

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Name four kinds of proxy and what each one controls.

Show answer

Virtual - when the real object is created. Protection - whether a call is allowed. Caching - whether the real call is needed at all. Remote - where the object actually runs.

2. A Spring bean calls its own @Transactional method through this. Does a transaction start?

Show answer

No. The call goes straight to the real object and never passes through the proxy that starts transactions. Move the method to another bean, or call it through the proxy.

3. Proxy and Decorator both share the interface and wrap an object. What is the difference?

Show answer

Intent. A proxy controls access to one subject and is usually invisible to the client, often managing the subject's lifecycle itself. A decorator adds behavior and is composed deliberately by the client.

Key takeaways

  • A proxy has the same interface as the real object and controls access to it.
  • Kinds: virtual (lazy), protection (security), caching, remote and logging.
  • Calls that don't go through the proxy (self-invocation) skip its behavior.
  • Caching proxies need an invalidation strategy.
  • Decorator adds behavior; Proxy controls access.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You need to control...Proxy kind
When an expensive object is createdvirtual (lazy)
Whether a call is allowedprotection
Whether the real call is neededcaching
Where the object runsremote (stub)
Visibility into callslogging

Snippets worth knowing by heart

Java

final class LazyImageProxy implements Image {
    private final String path;
    private volatile HighResImage real;
    LazyImageProxy(String path) { this.path = path; }

    public void display() {
        HighResImage r = real;
        if (r == null) {
            synchronized (this) {
                r = real;
                if (r == null) real = r = new HighResImage(path);   // loaded once, even under concurrency
            }
        }
        r.display();
    }
}

Python

class LazyImageProxy(Image):
    def __init__(self, path: str):
        self._path = path
        self._real: HighResImage | None = None
        self._lock = threading.Lock()

    def display(self) -> None:
        if self._real is None:
            with self._lock:
                if self._real is None:
                    self._real = HighResImage(self._path)
        self._real.display()

The traps

  • Self-invocation skips the proxy.
  • Lazy loading after the session closes.
  • N+1 queries from lazy proxies in loops.
  • Caches without invalidation.
  • Chatty remote calls.

Staff-level points, one line each

  • JDK dynamic proxies route equals/hashCode/toString through the handler too - a careless handler crashed hashCode.
  • Subclass proxies (CGLIB) can't intercept final/private methods.
  • Python __getattr__ proxies miss special methods and fail isinstance unless built carefully (wrapt).
  • An unsynchronized lazy proxy loaded twice under 8 threads; double-checked locking loaded once.
  • 100 orders with lazy customers: 101 queries; a join: 1 - use fetch joins / selectinload.
  • A cache stampede: 20 concurrent misses made 20 backend calls; single-flight made 1; add jittered TTLs and early refresh.
  • Protection proxies must not return the real object.
  • Remote proxies hide the network: coarse-grained calls, timeouts, idempotent retries.

Say it in an interview

  • "The gallery holds cheap proxies; only opened photos are loaded."
  • "That @Transactional method is called on this, so the proxy never sees it."
  • "Listing orders fetches customers in the same query - no lazy loading in the loop."
  • "Concurrent misses for the same key share one backend call."

Practice

This skill comes with 10 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Proxy Pattern free

A Caching Proxy With Expiry

MediumProCoding problemJava · Python

SlowWeatherService is expensive to call. CachingWeatherProxy implements the same interface and sits in front of it, so callers can't tell the difference - but it answers from memory whenever it can.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Guard a Document Store With a Protection Proxy

MediumProCoding problemJava · Python

The document store has no idea who is calling it - and it shouldn't have to. Put security in a protection proxy that implements the same DocumentStore interface:

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Proxy Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime