← Back to principles

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 interface
2interface Image {
3 display(): void;
4}
5
6// Real subject
7class RealImage implements Image {
8 private filename: string;
9
10 constructor(filename: string) {
11 this.filename = filename;
12 this.loadFromDisk(); // Expensive operation
13 }
14
15 private loadFromDisk(): void {
16 console.log(`

Protection Proxy (Access Control)

1// Subject interface
2interface BankAccount {
3 deposit(amount: number): void;
4 withdraw(amount: number): void;
5 getBalance(): number;
6}
7
8// Real subject
9class RealBankAccount implements BankAccount {
10 private balance: number = 0;
11
12 deposit(amount: number): void {
13 this.balance += amount;
14 console.log

Caching Proxy

1// Subject interface
2interface DataService {
3 fetchData(key: string): Promise<string>;
4}
5
6// Real subject
7class RealDataService implements DataService {
8 async fetchData(key: string): Promise<string> {
9 console.log(`Fetching data for key: ${key}`);
10 // Simulate expensive network call
11 await new Promise(resolve => resolve

Remote Proxy

1// Subject interface
2interface RemoteService {
3 processRequest(data: string): Promise<string>;
4}
5
6// Real subject (on remote server)
7class RemoteServiceImpl implements RemoteService {
8 async processRequest(data: string): Promise<string> {
9 // Process on remote server
10 return `Processed: ${data}`;
11 }
12}
13
14// 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 image
2class ProxyImage implements Image {
3 private realImage: RealImage | null = null;
4
5 display(): void {
6 if (this.realImage === null) {
7 this.realImage = new RealImage(); // Lazy loading
8 }
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;
3
4 display(): void {
5 if (this.realImage === null) {
6 this.realImage = new RealImage(); // Load only when needed
7 }
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 call
4 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 cached
5 }
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 interface
2interface 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}
7
8// Real cache service (Redis, Memcached, etc.)
9class RealCacheService implements CacheService {
10 async get(key: string

Features:

  1. Load balancing: Round-robin across servers
  2. Failover: Try next server on failure
  3. Local caching: Cache locally for performance
  4. Consistency: Write to all servers
  5. Timeout handling: Handle network timeouts
  6. 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.

Keep exploring

Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.