Structural PatternINTERMEDIATE7 min deep dive

Proxy Pattern Pattern

Provides a surrogate or placeholder for another object to control access to it.

Real-World Mental Model / Metaphor:

A debit card is a proxy for your bank account cash. You swipe the card instead of carrying paper bills; the card authenticates you, verifies balance, and delegates to the real account.

Interactive Runtime Simulator

Test and tweak inputs dynamically

Interactive Proxy Pattern SimulatorLIVE PLAYGROUND

Interact with the controls below to see how this design pattern executes in memory at runtime.

Category: Structural
Configure Chain Handlers (Toggle Failures):
CHAIN PIPELINE STATUS
Step 1: AuthHandler.check() -> PASSED
Step 2: RateLimitHandler.check() -> PASSED
Step 3: PayloadGuard.check() -> PASSED
The Problem It Solves

Needing to add security checks, caching, lazy loading, or logging to an object without modifying the object's original code.

The Architectural Solution

Create a Proxy that implements the same interface as the real subject, intercepts requests, performs auxiliary logic, and delegates to the real subject.

Pattern Structure & Participants

ParticipantRole & ResponsibilityType
Subject (Interface)Common interface for RealSubject and Proxyinterface
RealSubjectPerforms the heavy/sensitive business logicclass
ProxyControls access, caches results, or checks authclass
Collaboration Rules:
  • Client interacts with Proxy via Subject interface
  • Proxy intercepts call -> checks cache/auth -> forwards to RealSubject

Production Code Implementation

Proxy Pattern.java
public interface VideoDownloader {
    byte[] downloadVideo(String videoUrl);
}

// Real Heavy Subject
public class YouTubeDownloader implements VideoDownloader {
    @Override
    public byte[] downloadVideo(String videoUrl) {
        System.out.println("Connecting to YouTube CDN, downloading high-res video: " + videoUrl);
        return new byte[1024]; // Simulating heavy 50MB download
    }
}

// Caching Proxy
public class CachedVideoProxy implements VideoDownloader {
    private final VideoDownloader realDownloader;
    private final Map<String, byte[]> memoryCache = new ConcurrentHashMap<>();

    public CachedVideoProxy(VideoDownloader realDownloader) {
        this.realDownloader = realDownloader;
    }

    @Override
    public byte[] downloadVideo(String videoUrl) {
        if (memoryCache.containsKey(videoUrl)) {
            System.out.println("CACHE HIT: Returning video bytes from memory cache.");
            return memoryCache.get(videoUrl);
        }

        System.out.println("CACHE MISS: Delegating to Real YouTube Downloader...");
        byte[] video = realDownloader.downloadVideo(videoUrl);
        memoryCache.put(videoUrl, video);
        return video;
    }
}
When to Use:
  • •Lazy loading (Virtual Proxy): creating a heavy object only when it is actually needed.
  • •Access control (Protection Proxy): checking permissions before delegating to the real object.
  • •Caching (Caching Proxy): storing expensive request results in memory.
  • •Logging / Auditing (Logging Proxy): keeping a trace of all calls to the subject.
When to Avoid (Anti-Pattern):
  • •When direct access is already lightweight and no interceptor logic is needed.

Staff Engineering Interview Insights & Gotchas

  • Explain how Spring AOP uses JDK Dynamic Proxies (for interfaces) and CGLIB (for concrete classes) to implement `@Transactional`.
  • Highlight how Hibernate lazy-loading uses proxies to prevent fetching deep database tables until a getter is called.