Structural Pattern Pattern Deep Dive

Proxy

Sometimes you want code to talk to an object exactly as usual, but with something extra happening in between — a delay before it's really created, a permission check before it's used. The Proxy pattern answers the question: how do you control access to an object — lazily creating it, checking permissions, adding caching — without changing the code that uses it?

⚠️ The Problem

Say a photo gallery needs to display thumbnails for a folder of huge, high-resolution images. Loading every full image from disk the moment the gallery is constructed — even images the user never scrolls to — wastes time and memory. Separately, imagine a BankAccount.withdraw() method: every caller of withdraw() would need to remember to check the current user's permissions first, and that check is easy to forget at some call site, or to implement slightly differently in two different places.

✅ The Solution

Create a Proxy class that implements the exact same interface as the real object. Callers talk to the proxy exactly as they would talk to the real thing — they don't know, and don't need to know, the difference. Internally, the proxy controls access: a lazy-loading image proxy only loads the real image from disk the first time display() is actually called; a protection proxy checks permissions before allowing a withdrawal to go through to the real account, and rejects it otherwise. The access-control logic lives in exactly one place — the proxy — instead of being repeated by every caller.

Example 1 — a lazy-loading image proxy

Primary Example — JavaScript
// Shared interface: both the real image and the proxy expose display().
class RealImage {
  constructor(filename) {
    this.filename = filename;
    this._loadFromDisk(); // expensive — happens immediately on construction
  }
  _loadFromDisk() {
    console.log(`Loading ${this.filename} from disk (slow, expensive)`);
  }
  display() {
    console.log(`Displaying ${this.filename}`);
  }
}

// Proxy: same interface, but defers creating the real image until display() is actually called.
class LazyImageProxy {
  constructor(filename) {
    this.filename = filename;
    this.realImage = null; // not created yet
  }
  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.filename); // only loads on first use
    }
    this.realImage.display();
  }
}

// Client code treats every image identically — no idea which ones are proxies.
const gallery = [
  new LazyImageProxy("sunset.jpg"),
  new LazyImageProxy("mountains.jpg"),
  new LazyImageProxy("beach.jpg"),
];

console.log("Gallery constructed — nothing loaded from disk yet.");
gallery[1].display(); // only "mountains.jpg" gets loaded, right now, on first display

Constructing the whole gallery of proxies costs almost nothing — the expensive _loadFromDisk() work only happens for the one image the user actually scrolls to and displays, and only the first time.

Example 2 — a protection proxy for a bank account

Secondary Example — C#
public interface IBankAccount
{
    void Withdraw(decimal amount);
}

// The real object — no permission logic here; it just does the withdrawal.
public class BankAccount : IBankAccount
{
    private decimal _balance;

    public BankAccount(decimal initialBalance)
    {
        _balance = initialBalance;
    }

    public void Withdraw(decimal amount)
    {
        _balance -= amount;
        Console.WriteLine($"Withdrew {amount:C}. New balance: {_balance:C}");
    }
}

// Proxy: same interface, checks permission before delegating to the real account.
public class ProtectedBankAccountProxy : IBankAccount
{
    private readonly BankAccount _realAccount;
    private readonly string _currentUserRole;

    public ProtectedBankAccountProxy(BankAccount realAccount, string currentUserRole)
    {
        _realAccount = realAccount;
        _currentUserRole = currentUserRole;
    }

    public void Withdraw(decimal amount)
    {
        if (_currentUserRole != "owner")
        {
            Console.WriteLine("Access denied: only the account owner can withdraw funds.");
            return;
        }
        _realAccount.Withdraw(amount);
    }
}

// Client code depends only on IBankAccount — the permission check is invisible to it.
IBankAccount account = new ProtectedBankAccountProxy(new BankAccount(1000m), "guest");
account.Withdraw(200m); // denied — caller never wrote the check itself

Every caller that goes through ProtectedBankAccountProxy gets the same permission check, enforced in one place, instead of trusting each call site to remember it.

👍 When To Use It

  • Creating or loading the real object is expensive, and you want to defer that cost until it's genuinely needed (a "virtual proxy").
  • You need to control access to an object — permission checks, rate limiting, logging — without scattering that logic across every caller (a "protection proxy").
  • You want to add caching, or route calls to a remote object over the network, while keeping the calling code exactly as if it were talking to a plain local object.

👎 When NOT To Use It

  • When the real object is cheap to create and access is already unrestricted — a proxy adds an indirection layer with nothing to actually control.
  • When you actually want to add new behavior or responsibilities, not just control access to existing behavior — that's Decorator's job, not Proxy's.
  • When the access-control logic varies wildly per caller — a single proxy enforcing one consistent policy stops making sense if every caller needs different rules.

❓ FAQ

How is Proxy different from Decorator? Both wrap an object behind the same interface.

Proxy controls access to an object that implements the same interface — it often manages that object's lifecycle or creation (lazy-loading it, checking permission before forwarding a call) and typically the calling code shouldn't be able to tell a proxy from the real thing. Decorator adds new behavior or responsibilities to an object, and the wrapped object usually already fully exists and works — decoration is about enrichment, not gatekeeping. In practice the two are structurally similar; the difference is intent.

Does the client need to know it's talking to a proxy instead of the real object?

Ideally, no — that's the point of implementing the exact same interface. Client code should work unchanged whether it's handed a RealImage or a LazyImageProxy. If callers need special-case handling depending on which one they got, the proxy has failed to be transparent.

Are ORMs' "lazy-loaded" related entities an example of Proxy?

Yes, commonly — many ORMs return a proxy object in place of a related entity, and only issue the actual database query the first time a property on that proxy is accessed. That's a textbook virtual proxy: the interface looks identical to the real entity, but the expensive work (the query) is deferred until it's genuinely needed.

🙋 There Are No Dumb Questions

Q: If the proxy and the real object have the exact same interface, why not just put the permission check or lazy-loading logic straight into the real class?
A: You could, for a single case — but mixing "is the caller allowed to do this" or "has this loaded yet" logic into the same class as the real behavior means every class that needs access control has to reimplement it, and the real class can no longer be used in contexts that don't want that control (like internal trusted code). Keeping it in a separate proxy keeps the real class focused on its actual job, and lets you add or remove the control layer without touching it.

✅ Quick Check

What is the defining purpose of a Proxy, as opposed to a Decorator?


That's all seven Structural patterns. Next up: the first of the Behavioral patterns — what happens when a request needs to pass along a chain of handlers until one of them decides to handle it? That's Chain of Responsibility.

← Back to
Next →