Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Design Patterns (GoF and modern)  Module 3 – Structural patterns: Adapter to Proxy

Proxy: control access to another object

The Proxy pattern puts a stand-in with the same interface in front of an object, to create it lazily, cache, log or check calls, and shows where proxies stop.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 7 of 30 minutes free
Free: One interface, a gatekeeper in front. With a pass: the other 9 sections and 3 interview answers, about 23 minutes.

What you will learn

  • Implement virtual, protection, caching and logging proxies behind the subject's interface
  • Use language-level proxies such as JavaScript's Proxy with Reflect and Python attribute delegation
  • Explain why an in-process protection proxy is not a security boundary
  • Distinguish Proxy from Decorator and Adapter by intent

Before you start

What you’ll learn · Before you start

What you will learn

  • Implement virtual, protection, caching and logging proxies behind the subject's interface
  • Use language-level proxies such as JavaScript's Proxy with Reflect and Python attribute delegation
  • Explain why an in-process protection proxy is not a security boundary
  • Distinguish Proxy from Decorator and Adapter by intent

Before you start

On this page

A proxy is an object that stands in for another object, its subject, behind exactly the same interface. Callers talk to the proxy as if it were the subject, and the proxy decides when and how each call reaches the real thing. It can create the subject only when it is first needed, check that the caller may make the call, answer from a cache, record the call, or forward it to another process. Use a proxy when you need that kind of control over an object without changing the object or the code that calls it.

One interface, a gatekeeper in front

Because the proxy has the subject’s interface, code written against that interface cannot tell the two apart, and that is what makes the pattern useful. A gallery that draws pictures through an Image interface does not care whether it holds a decoded image or a placeholder that decodes itself on the first draw.

A gallery calls draw on a LazyImage proxy; the proxy and the real DiskImage both implement Image, and the proxy forwards to the real one.Gallery (client)holds an ImageLazyImage (proxy)implements ImageDiskImage (real subject)implements Imagedraw(x, y)creates it on the first draw,then forwards every draw

The client sees only the interface; the proxy stands in front of the subject

Text description of the diagram

Three boxes are stacked from top to bottom.

At the top is the client, a gallery, which holds an object it knows only as an Image and calls draw on it with an x and a y position.

The arrow goes to the middle box, LazyImage, the proxy, which implements the Image interface.

A second arrow goes from the proxy down to the bottom box, DiskImage, the real subject, which also implements Image and reads and decodes a file. The proxy creates the DiskImage on the first draw and then forwards every draw to it. The gallery cannot tell which of the two it holds.

Here is that placeholder, a virtual proxy, in a gallery of 100 images of which three fit on the screen:

A gallery of 100 lazy images Python · opening/lazy_image.py
"""A virtual proxy: a gallery of 100 images that loads only the ones it actually draws."""


class DiskImage:
    """The real subject: creating one reads and decodes the whole file, which is slow."""

    loads = 0

    def __init__(self, path):
        DiskImage.loads += 1
        self.path = path

    def draw(self, x, y):
        return f"{self.path} drawn at {x},{y}"


class LazyImage:
    """The proxy: the same draw() method, but the real image is created on the first draw only."""

    def __init__(self, path):
        self.path = path
        self._real = None

    def draw(self, x, y):
        if self._real is None:
            self._real = DiskImage(self.path)
        return self._real.draw(x, y)


gallery = [LazyImage(f"photo{n}.jpg") for n in range(1, 101)]
print("gallery of", len(gallery), "images, files loaded:", DiskImage.loads)

for row, image in enumerate(gallery[:3]):  # only three fit on the screen
    print(image.draw(0, row * 200))
print(gallery[0].draw(0, 0))  # drawn again: no second load

print("files loaded:", DiskImage.loads)

Output

gallery of 100 images, files loaded: 0
photo1.jpg drawn at 0,0
photo2.jpg drawn at 0,200
photo3.jpg drawn at 0,400
photo1.jpg drawn at 0,0
files loaded: 3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lazy_image.py

Building the gallery loaded nothing. Drawing the three visible images loaded three files, and drawing the first one again loaded nothing more, because the proxy kept the real image once it had made it. A gallery that decoded all 100 images up front would have done 97 loads that nobody looked at.

The same two counts check any proxy: count the calls that clients make and the calls that reach the subject. The difference is the work the proxy saved, or the work it refused, and a test can assert both numbers.

Proxies are named after the control they add:

  • A virtual proxy creates an expensive subject only when it is first used.
  • A protection proxy checks whether the caller may make a call before passing it on.
  • A caching proxy answers repeated requests from stored results instead of asking the subject again.
  • A logging or counting proxy, sometimes called a smart reference, records each call and passes it on unchanged.
  • A remote proxy stands in for an object in another process and turns each call into a network request.

Some languages build proxies in. JavaScript’s Proxy object sends operations on another object to handler functions, Java’s java.lang.reflect.Proxy creates proxy classes for interfaces at run time, and Python forwards unknown attributes with __getattr__.

A decorator has the same shape, an object that wraps another behind the same interface, but a different purpose: it adds behaviour and is meant to be stacked, while a proxy controls access to one subject. A proxy has a cost too: every call passes through one more object, and a proxy that hides slow or failing work, such as a network call, makes that work look as cheap as a local method call.

Three proxies behind one interface, and why their order matters Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Language-level proxies: JavaScript’s Proxy and Reflect
  • Python delegation with __getattr__, and the operators it misses
  • Why an in-process protection proxy is not a security boundary
  • A caching proxy under load: one load per key, and no cached errors
    • A caching proxy for a slow exchange-rate client
  • Remote proxies: what a generated client stub hides
  • Proxy, Decorator or Adapter: telling them apart by intent
  • Key takeaways
  • Check yourself
  • Interview questions
    • A proxy and a decorator both wrap an object behind the same interface. What is the difference?
    • Checkout looks up an exchange rate 2,000 times a second, but the rate service allows 50 requests a second and takes about 50 ms to answer. Design a caching proxy for it. What do you cache, for how long, and what happens at expiry and when the service fails?
    • A teammate protects account closure with a proxy that checks the user's role before calling AccountService.close(). Is that enough? Where would you enforce the rule, and what is the proxy still good for?
    • Your service calls another service through a generated gRPC client stub that looks like a local object. Which failures can that call have that a local call cannot, and how should the calling code handle each one?

5 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 23 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.