Design Principle
Proxy Pattern
Learn the Proxy pattern: provide a surrogate or placeholder for another object to control access to it. Understand virtual, protection, and remote proxies.
Proxy Pattern
Why This Matters
Think of the Proxy pattern like a receptionist at a company. You don't talk directly to the CEO—you talk to the receptionist, who controls access. The receptionist might check if you have an appointment, cache information, or forward your request. The Proxy pattern does the same for code—it provides a placeholder that controls access to the real object.
This matters because sometimes you need to control access to objects: lazy loading (don't create expensive objects until needed), caching (cache results from expensive operations), security (check permissions before access), remote access (proxy handles network communication). The Proxy pattern provides a clean way to add these features without changing the real object.
In interviews, when someone asks "How would you implement lazy loading?", they're testing whether you understand the Proxy pattern. Do you know how to defer object creation? Can you add caching or security? Most engineers can't. They create objects eagerly and wonder why startup is slow.
What Engineers Usually Get Wrong
Engineers often think "Proxy pattern is just wrapping objects." But Proxy pattern is more specific—it maintains the same interface as the real object, so clients can't tell the difference. This is different from Adapter (converts interface) or Decorator (adds behavior). Understanding the difference helps you choose the right pattern.
Engineers also don't understand when to use Proxy. Use Proxy when you need to control access (lazy loading, caching, security, remote access). Don't use it when you can access the object directly—proxies add indirection. Use Proxy when the indirection provides value (performance, security, remote access).
How This Breaks Systems in the Real World
A service was loading large images eagerly at startup. Each image was 10MB, and there were 100 images. Startup took 5 minutes and used 1GB of memory. The fix? Use Proxy pattern. Create image proxies that load images lazily. Now startup is fast, and images load only when needed. This improves startup time and memory usage.
Another story: A service was making expensive database queries repeatedly. The same query was executed 100 times per second, each taking 100ms. The database was overwhelmed. The fix? Use Proxy pattern. Create a caching proxy that caches query results. Now the query executes once, and subsequent calls use the cache. This reduces database load significantly.
What is the Proxy Pattern?
Proxy Pattern provides:
- Access control: Control access to the real object
- Lazy loading: Defer expensive object creation
- Caching: Cache results from expensive operations
- Security: Add security checks before access
Types:
- Virtual Proxy: Lazy loading
- Protection Proxy: Access control
- Remote Proxy: Network communication
- Caching Proxy: Cache results
Structure
Subject (interface)
└─ request()
RealSubject (implements Subject)
└─ request()
Proxy (implements Subject)
└─ realSubject: RealSubject
└─ request() (controls access, calls realSubject.request())
Examples
Virtual Proxy (Lazy Loading)
1// Subject interface2interface Image {3 display(): void;4}56// Real subject7class RealImage implements Image {8 private filename: string;910 constructor(filename: string) {11 this.filename = filename;12 this.loadFromDisk(); // Expensive operation13 }1415 private loadFromDisk(): void {16 console.log(`
Protection Proxy (Access Control)
1// Subject interface2interface BankAccount {3 deposit(amount: number): void;4 withdraw(amount: number): void;5 getBalance(): number;6}78// Real subject9class RealBankAccount implements BankAccount {10 private balance: number = 0;1112 deposit(amount: number): void {13 this.balance += amount;14 console.log
Caching Proxy
1// Subject interface2interface DataService {3 fetchData(key: string): Promise<string>;4}56// Real subject7class RealDataService implements DataService {8 async fetchData(key: string): Promise<string> {9 console.log(`Fetching data for key: ${key}`);10 // Simulate expensive network call11 await new Promise(resolve => resolve
Remote Proxy
1// Subject interface2interface RemoteService {3 processRequest(data: string): Promise<string>;4}56// Real subject (on remote server)7class RemoteServiceImpl implements RemoteService {8 async processRequest(data: string): Promise<string> {9 // Process on remote server10 return `Processed: ${data}`;11 }12}1314// Remote proxy (client-side)15class RemoteProxy implements
Common Pitfalls
- Over-proxying: Too many proxy layers. Fix: Use only when needed
- Performance overhead: Proxy adds overhead. Fix: Consider performance impact
- Tight coupling: Proxy tightly coupled to real subject. Fix: Use interfaces
- Not handling errors: Proxy doesn't handle errors properly. Fix: Add error handling
Interview Questions
Beginner
Q: What is the Proxy pattern and what are its use cases?
A:
Proxy Pattern provides a surrogate or placeholder for another object to control access to it.
Key characteristics:
- Surrogate: Acts as intermediary between client and real object
- Access control: Controls access to real object
- Same interface: Implements same interface as real subject
Types:
- Virtual Proxy: Lazy loading (defer expensive creation)
- Protection Proxy: Access control (security checks)
- Remote Proxy: Network communication (remote objects)
- Caching Proxy: Cache results (avoid repeated operations)
Use cases:
- Lazy loading: Defer expensive object creation
- Access control: Add security checks
- Caching: Cache expensive operations
- Remote access: Access remote objects locally
Example:
1// Proxy controls access to real image2class ProxyImage implements Image {3 private realImage: RealImage | null = null;45 display(): void {6 if (this.realImage === null) {7 this.realImage = new RealImage(); // Lazy loading8 }9 this.realImage.display();10 }11}
Intermediate
Q: Explain different types of proxies. How do you implement a virtual proxy for lazy loading?
A:
Types of Proxies:
1. Virtual Proxy (Lazy Loading):
1class ProxyImage implements Image {2 private realImage: RealImage | null = null;34 display(): void {5 if (this.realImage === null) {6 this.realImage = new RealImage(); // Load only when needed7 }8 this.realImage.display();9 }10}
Use: Defer expensive object creation
2. Protection Proxy (Access Control):
1class ProtectionProxy implements BankAccount {2 deposit(amount: number): void {3 if (this.hasPermission()) {4 this.realAccount.deposit(amount);5 } else {6 throw new Error("Permission denied");7 }8 }9}
Use: Add security checks
3. Remote Proxy:
1class RemoteProxy implements Service {2 async process(data: string): Promise<string> {3 // Make network call4 return await fetch("/api/process", { body: data });5 }6}
Use: Access remote objects locally
4. Caching Proxy:
1class CachingProxy implements DataService {2 async fetch(key: string): Promise<string> {3 if (this.cache.has(key)) {4 return this.cache.get(key); // Return cached5 }6 const data = await this.realService.fetch(key);7 this.cache.set(key, data);8 return data;9 }
Use: Cache expensive operations
Senior
Q: Design a proxy system for a distributed caching layer that handles cache invalidation, load balancing, and failover. How do you ensure consistency and handle network failures?
A:
1// Subject interface2interface CacheService {3 get(key: string): Promise<string | null>;4 set(key: string, value: string, ttl: number): Promise<void>;5 delete(key: string): Promise<void>;6}78// Real cache service (Redis, Memcached, etc.)9class RealCacheService implements CacheService {10 async get(key: string
Features:
- Load balancing: Round-robin across servers
- Failover: Try next server on failure
- Local caching: Cache locally for performance
- Consistency: Write to all servers
- Timeout handling: Handle network timeouts
- Cache invalidation: Invalidate by pattern
-
Proxy pattern: Provides surrogate to control access to object
-
Types: Virtual (lazy loading), Protection (access control), Remote (network), Caching (cache)
-
Benefits: Access control, lazy loading, caching, security
-
Use cases: Lazy loading, access control, remote access, caching
-
Best practices: Use interfaces, handle errors, consider performance
-
Adapter Pattern - Both patterns wrap objects. Proxy controls access, Adapter converts interface. Understanding both helps choose the right pattern.
-
Decorator Pattern - Both patterns wrap objects. Proxy controls access, Decorator adds behavior. Understanding both helps choose the right pattern.
-
SOLID Principles - Proxy pattern follows Dependency Inversion Principle (depend on abstractions). Understanding SOLID helps understand proxy benefits.
-
Separation of Concerns - Proxies separate access control from business logic. Understanding separation of concerns helps understand proxy benefits.
-
Factory Pattern - Factories can create proxies. Understanding factories helps understand proxy creation patterns.
Key Takeaways
Proxy pattern: Provides surrogate to control access to object
Types: Virtual (lazy loading), Protection (access control), Remote (network), Caching (cache)
Benefits: Access control, lazy loading, caching, security
Use cases: Lazy loading, access control, remote access, caching
Best practices: Use interfaces, handle errors, consider performance
Related Topics
Adapter Pattern
Both patterns wrap objects. Proxy controls access, Adapter converts interface. Understanding both helps choose the right pattern.
Decorator Pattern
Both patterns wrap objects. Proxy controls access, Decorator adds behavior. Understanding both helps choose the right pattern.
SOLID Principles
Proxy pattern follows Dependency Inversion Principle (depend on abstractions). Understanding SOLID helps understand proxy benefits.
Separation of Concerns
Proxies separate access control from business logic. Understanding separation of concerns helps understand proxy benefits.
Factory Pattern
Factories can create proxies. Understanding factories helps understand proxy creation patterns.
Keep exploring
Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.