Back to All Design Patterns
Pattern 8 of 22
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 dynamicallyInteractive Proxy Pattern SimulatorLIVE PLAYGROUND
Interact with the controls below to see how this design pattern executes in memory at runtime.
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
| Participant | Role & Responsibility | Type |
|---|---|---|
| Subject (Interface) | Common interface for RealSubject and Proxy | interface |
| RealSubject | Performs the heavy/sensitive business logic | class |
| Proxy | Controls access, caches results, or checks auth | class |
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.