Skip to content

Proxy Pattern

Control access to objects - provide a placeholder or surrogate for another object!

Now let’s explore the Proxy Pattern - a powerful structural design pattern that provides a placeholder or surrogate for another object to control access to it.

Imagine you’re accessing a large file on a remote server. Instead of loading the entire file immediately (which is slow and expensive), you use a proxy that loads the file only when you actually need it. The Proxy Pattern works the same way - it provides a placeholder that controls access to the real object, adding functionality like lazy loading, access control, or caching.

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. The proxy has the same interface as the real object, allowing it to be used interchangeably.

The Proxy Pattern is useful when:

  1. You want lazy loading - Load expensive objects only when needed
  2. You want access control - Control who can access the object
  3. You want caching - Cache results to avoid expensive operations
  4. You want logging/monitoring - Track access to objects
  5. You want remote access - Access objects on remote servers

What Happens If We Don’t Use Proxy Pattern?

Section titled “What Happens If We Don’t Use Proxy Pattern?”

Without the Proxy Pattern, you might:

  • Load everything upfront - Expensive objects loaded even if not used
  • No access control - Direct access to sensitive objects
  • No caching - Expensive operations repeated unnecessarily
  • Tight coupling - Clients directly depend on real objects
  • Hard to add cross-cutting concerns - Logging, security scattered everywhere

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Proxy Pattern works in practice - showing how proxy controls access:

sequenceDiagram
    participant Client
    participant Proxy as ImageProxy
    participant RealImage
    
    Client->>Proxy: display()
    activate Proxy
    Proxy->>Proxy: Check if image loaded
    alt Image not loaded
        Proxy->>RealImage: load_from_disk()
        activate RealImage
        RealImage->>RealImage: Expensive loading operation
        RealImage-->>Proxy: Image loaded
        deactivate RealImage
        Proxy->>Proxy: Cache reference
    end
    Proxy->>RealImage: display()
    activate RealImage
    RealImage-->>Proxy: Image displayed
    deactivate RealImage
    Proxy-->>Client: Displayed
    deactivate Proxy
    
    Note over Client,RealImage: Proxy loads image only\nwhen needed (lazy loading)!

You’re building an image viewer. Loading images from disk is expensive. Without Proxy Pattern:

bad_image_viewer.py
# ❌ Without Proxy Pattern - Loads everything upfront!
class RealImage:
"""Real image - expensive to load"""
def __init__(self, filename: str):
self.filename = filename
self._load_from_disk() # Bad: Loads immediately!
def _load_from_disk(self):
"""Expensive operation - loads image from disk"""
print(f"Loading {self.filename} from disk... (expensive operation)")
# Simulate expensive loading
import time
time.sleep(1) # Simulate disk I/O
def display(self):
"""Display the image"""
print(f"Displaying {self.filename}")
# Problem: All images loaded immediately, even if not displayed!
def main():
# Bad: Loads all images upfront, even if not used!
images = [
RealImage("photo1.jpg"), # Loaded immediately
RealImage("photo2.jpg"), # Loaded immediately
RealImage("photo3.jpg"), # Loaded immediately
]
# Only display first image, but all were loaded!
images[0].display()
# Problems:
# - All images loaded upfront (wasteful!)
# - Slow startup time
# - High memory usage
# - No lazy loading

Problems:

  • Loads everything upfront - All images loaded even if not used
  • Slow startup - Expensive operations happen immediately
  • High memory usage - All objects in memory at once
  • No lazy loading - Can’t defer expensive operations
classDiagram
    class Subject {
        <<interface>>
        +request() void
    }
    class RealSubject {
        +request() void
    }
    class Proxy {
        -real_subject: RealSubject
        +request() void
        -check_access() bool
        -lazy_load() void
    }
    class Client {
        +operation() void
    }
    
    Client --> Subject : uses
    Proxy --> Subject : implements
    Proxy --> RealSubject : controls access
    RealSubject --> Subject : implements
    
    note for Proxy "Controls access to RealSubject"
    note for RealSubject "Expensive object"
proxy_image_viewer.py
from abc import ABC, abstractmethod
# Step 1: Define the Subject interface
class Image(ABC):
"""Subject interface - what images should do"""
@abstractmethod
def display(self) -> None:
"""Display the image"""
pass
# Step 2: Implement Real Subject
class RealImage(Image):
"""Real image - expensive to load"""
def __init__(self, filename: str):
self.filename = filename
self._load_from_disk()
def _load_from_disk(self):
"""Expensive operation - loads image from disk"""
print(f"Loading {self.filename} from disk... (expensive operation)")
# Simulate expensive loading
import time
time.sleep(1) # Simulate disk I/O
def display(self):
"""Display the image"""
print(f"Displaying {self.filename}")
# Step 3: Create Proxy
class ImageProxy(Image):
"""Proxy - controls access to real image"""
def __init__(self, filename: str):
self.filename = filename
self._real_image: RealImage = None # Lazy loading - not loaded yet!
def display(self):
"""Display image - loads only when needed"""
if self._real_image is None:
# Lazy loading - load only when actually needed!
print(f"Proxy: Loading {self.filename} on demand...")
self._real_image = RealImage(self.filename)
# Delegate to real image
self._real_image.display()
# Usage - Lazy loading!
def main():
print("Creating image proxies (no loading yet)...\n")
# Good: Only creates proxies, doesn't load images!
images = [
ImageProxy("photo1.jpg"), # Proxy created, image not loaded
ImageProxy("photo2.jpg"), # Proxy created, image not loaded
ImageProxy("photo3.jpg"), # Proxy created, image not loaded
]
print("Proxies created. No images loaded yet!\n")
# Only load when actually needed!
print("Displaying first image (loads on demand):")
images[0].display()
print("\nDisplaying first image again (uses cached):")
images[0].display() # Uses cached real image
print("\n✅ Proxy Pattern: Images loaded only when needed (lazy loading)!")
if __name__ == "__main__":
main()

Real-World Software Example: Database Query Proxy

Section titled “Real-World Software Example: Database Query Proxy”

Now let’s see a realistic software example - a database system that needs to cache query results to avoid expensive database calls.

You’re building a database access layer. Database queries are expensive. Without Proxy Pattern:

bad_database_access.py
# ❌ Without Proxy Pattern - No caching!
class Database:
"""Real database - expensive queries"""
def execute_query(self, query: str) -> dict:
"""Expensive database query"""
print(f"Executing query: {query} (expensive operation)")
# Simulate expensive database operation
import time
time.sleep(2) # Simulate database I/O
# Return mock result
return {"result": f"Data for {query}", "rows": 100}
def get_user(self, user_id: int) -> dict:
"""Get user by ID"""
return self.execute_query(f"SELECT * FROM users WHERE id = {user_id}")
# Problem: Every query hits the database, even if same query!
def main():
db = Database()
# Same query executed multiple times - wasteful!
result1 = db.get_user(1) # Hits database
result2 = db.get_user(1) # Hits database again (same query!)
result3 = db.get_user(1) # Hits database again (same query!)
print("❌ No caching - same query executed 3 times!")
# Problems:
# - No caching - same queries executed repeatedly
# - Slow performance
# - Unnecessary database load

Problems:

  • No caching - Same queries executed repeatedly
  • Slow performance - Every query hits the database
  • Unnecessary load - Database accessed even for cached queries
proxy_database_access.py
from abc import ABC, abstractmethod
from typing import Dict, Any
import hashlib
import json
# Step 1: Define the Subject interface
class DatabaseInterface(ABC):
"""Subject interface - database operations"""
@abstractmethod
def get_user(self, user_id: int) -> Dict[str, Any]:
"""Get user by ID"""
pass
# Step 2: Implement Real Subject
class Database(DatabaseInterface):
"""Real database - expensive queries"""
def execute_query(self, query: str) -> Dict[str, Any]:
"""Expensive database query"""
print(f"Database: Executing query: {query} (expensive operation)")
# Simulate expensive database operation
import time
time.sleep(2) # Simulate database I/O
# Return mock result
return {"result": f"Data for {query}", "rows": 100}
def get_user(self, user_id: int) -> Dict[str, Any]:
"""Get user by ID"""
return self.execute_query(f"SELECT * FROM users WHERE id = {user_id}")
# Step 3: Create Proxy with Caching
class DatabaseProxy(DatabaseInterface):
"""Proxy - adds caching to database access"""
def __init__(self, database: Database):
self._database = database
self._cache: Dict[str, Dict[str, Any]] = {} # Cache for query results
def _get_cache_key(self, method: str, *args) -> str:
"""Generate cache key from method and arguments"""
key_data = {"method": method, "args": args}
key_string = json.dumps(key_data, sort_keys=True)
return hashlib.md5(key_string.encode()).hexdigest()
def get_user(self, user_id: int) -> Dict[str, Any]:
"""Get user by ID - with caching"""
cache_key = self._get_cache_key("get_user", user_id)
# Check cache first
if cache_key in self._cache:
print(f"Proxy: Cache hit for user {user_id} (fast!)")
return self._cache[cache_key]
# Cache miss - query database
print(f"Proxy: Cache miss for user {user_id}, querying database...")
result = self._database.get_user(user_id)
# Store in cache
self._cache[cache_key] = result
print(f"Proxy: Cached result for user {user_id}")
return result
def clear_cache(self):
"""Clear the cache"""
self._cache.clear()
print("Proxy: Cache cleared")
# Usage - With caching!
def main():
database = Database()
proxy = DatabaseProxy(database)
print("=== First call (cache miss) ===\n")
result1 = proxy.get_user(1) # Cache miss - queries database
print("\n=== Second call (cache hit) ===\n")
result2 = proxy.get_user(1) # Cache hit - returns cached result
print("\n=== Third call (cache hit) ===\n")
result3 = proxy.get_user(1) # Cache hit - returns cached result
print("\n✅ Proxy Pattern: Caching reduces database load!")
if __name__ == "__main__":
main()

There are different types of proxies based on their purpose:

Loads expensive objects only when needed:

virtual_proxy.py
# Virtual Proxy - lazy loading
class Proxy:
def __init__(self):
self._real_object = None # Not loaded yet
def operation(self):
if self._real_object is None:
self._real_object = ExpensiveObject() # Load on demand
return self._real_object.operation()

Use case: Lazy loading of expensive resources

Controls access to objects based on permissions:

protection_proxy.py
# Protection Proxy - access control
class ProtectionProxy:
def __init__(self, user_role: str):
self._real_object = RealObject()
self._user_role = user_role
def operation(self):
if self._user_role != "admin":
raise PermissionError("Access denied")
return self._real_object.operation()

Use case: Access control, security

Represents objects on remote servers:

remote_proxy.py
# Remote Proxy - remote access
class RemoteProxy:
def __init__(self, server_url: str):
self._server_url = server_url
def operation(self):
# Make remote call
response = requests.post(self._server_url, data={"operation": "do"})
return response.json()

Use case: Remote method invocation, RPC

Caches results to avoid expensive operations:

caching_proxy.py
# Caching Proxy - caching
class CachingProxy:
def __init__(self):
self._real_object = RealObject()
self._cache = {}
def operation(self, key: str):
if key in self._cache:
return self._cache[key] # Return cached
result = self._real_object.operation(key)
self._cache[key] = result # Cache result
return result

Use case: Caching, performance optimization


Use Proxy Pattern when:

You want lazy loading - Load expensive objects only when needed
You want access control - Control who can access the object
You want caching - Cache results to avoid expensive operations
You want logging/monitoring - Track access to objects
You want remote access - Access objects on remote servers

Don’t use Proxy Pattern when:

Simple objects - If object is simple, proxy adds unnecessary complexity
No need for control - If you don’t need lazy loading, caching, or access control
Performance critical - Proxy adds indirection (usually negligible)
Over-engineering - Don’t add complexity for simple cases


no_delegation.py
# ❌ Bad: Proxy doesn't delegate to real object
class Proxy:
def operation(self):
return "Proxy result" # Bad: Doesn't use real object!
# ✅ Good: Proxy delegates to real object
class Proxy:
def __init__(self):
self._real_object = None
def operation(self):
if self._real_object is None:
self._real_object = RealObject()
return self._real_object.operation() # Good: Delegates
different_interface.py
# ❌ Bad: Proxy has different interface
class Proxy:
def proxy_operation(self): # Bad: Different method name!
pass
# ✅ Good: Proxy has same interface
class Proxy(Subject):
def operation(self): # Good: Same interface
pass

Mistake 3: Creating Real Object Unnecessarily

Section titled “Mistake 3: Creating Real Object Unnecessarily”
unnecessary_creation.py
# ❌ Bad: Creates real object even if not needed
class Proxy:
def __init__(self):
self._real_object = RealObject() # Bad: Created immediately!
def operation(self):
return self._real_object.operation()
# ✅ Good: Creates real object only when needed
class Proxy:
def __init__(self):
self._real_object = None # Good: Not created yet
def operation(self):
if self._real_object is None:
self._real_object = RealObject() # Good: Created on demand
return self._real_object.operation()

  1. Lazy Loading - Loads expensive objects only when needed
  2. Access Control - Controls who can access objects
  3. Caching - Caches results to avoid expensive operations
  4. Remote Access - Accesses objects on remote servers
  5. Separation of Concerns - Adds cross-cutting concerns without modifying real object
  6. Transparent - Client doesn’t know it’s using a proxy

Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. The proxy has the same interface as the real object.

  • Lazy loading - Load expensive objects only when needed
  • Access control - Control who can access objects
  • Caching - Cache results to avoid expensive operations
  • Remote access - Access objects on remote servers
  • Separation of concerns - Add cross-cutting concerns
  1. Define subject interface - Common interface for proxy and real object
  2. Implement real subject - The actual object
  3. Create proxy - Implements same interface, controls access
  4. Client uses proxy - Client interacts with proxy (transparent)
  5. Proxy delegates - Proxy delegates to real object when needed
Client → Proxy → RealSubject
  • Subject - Interface for proxy and real object
  • RealSubject - The actual object
  • Proxy - Controls access to real subject
  • Client - Uses proxy (doesn’t know it’s a proxy)
class Proxy(Subject):
def __init__(self):
self._real_subject = None
def operation(self):
if self._real_subject is None:
self._real_subject = RealSubject() # Lazy load
return self._real_subject.operation()

✅ Lazy loading
✅ Access control
✅ Caching
✅ Remote access
✅ Logging/monitoring

❌ Simple objects
❌ No need for control
❌ Performance critical
❌ Over-engineering

  • Proxy Pattern = Controls access to real object
  • Proxy = Placeholder with same interface
  • RealSubject = Actual object
  • Benefit = Lazy loading, caching, access control
  • Use Case = Expensive resources, caching, security
class Proxy(Subject):
def __init__(self):
self._real_subject = None
def operation(self):
if self._real_subject is None:
self._real_subject = RealSubject()
return self._real_subject.operation()
  • Proxy Pattern controls access to real objects
  • It has the same interface as real object
  • It’s about control, not just wrapping!
  • Use it for lazy loading, caching, access control
  • Keep it transparent - client shouldn’t know it’s a proxy!

What to say:

“Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. The proxy has the same interface as the real object, allowing it to be used interchangeably while adding functionality like lazy loading, caching, or access control.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must mention:

  • Lazy loading - Load expensive objects only when needed
  • Access control - Control who can access objects
  • Caching - Cache results to avoid expensive operations
  • Remote access - Access objects on remote servers
  • Logging/monitoring - Track access to objects

Example scenario to give:

“I’d use Proxy Pattern when building an image viewer. Loading images from disk is expensive. Instead of loading all images upfront, I create image proxies that load images only when they’re actually displayed. This improves startup time and reduces memory usage.”

Must discuss:

  • Proxy: Controls access, same interface, adds functionality like lazy loading/caching
  • Decorator: Adds behavior, same interface, wraps to enhance functionality
  • Key difference: Proxy controls access, Decorator adds behavior

Example to give:

“Proxy Pattern controls access to an object and adds functionality like lazy loading or caching. Decorator Pattern adds behavior to an object dynamically. Proxy is about control and optimization, Decorator is about adding features. Proxy typically has one real object, Decorator can wrap multiple decorators.”

Must discuss:

  • Virtual Proxy: Lazy loading of expensive resources
  • Protection Proxy: Access control and security
  • Remote Proxy: Remote object access
  • Caching Proxy: Caching expensive operations

Example to give:

“I use Virtual Proxy for lazy loading images, Protection Proxy for access control in a secure system, Remote Proxy for accessing objects on remote servers, and Caching Proxy for caching database query results. Each variant serves a specific purpose.”

Benefits to mention:

  • Lazy loading - Loads expensive objects only when needed
  • Access control - Controls who can access objects
  • Caching - Caches results to avoid expensive operations
  • Separation of concerns - Adds cross-cutting concerns without modifying real object
  • Transparent - Client doesn’t know it’s using a proxy

Trade-offs to acknowledge:

  • Complexity - Adds abstraction layer
  • Performance - Small overhead (usually negligible)
  • Over-engineering risk - Can be overkill for simple cases

Q: “What’s the difference between Proxy Pattern and Decorator Pattern?”

A:

“Proxy Pattern controls access to an object and adds functionality like lazy loading, caching, or access control. Decorator Pattern adds behavior to an object dynamically. Proxy is about control and optimization, Decorator is about adding features. Proxy typically manages one real object, Decorator can wrap multiple decorators in a chain.”

Q: “When would you use Virtual Proxy vs Caching Proxy?”

A:

“I use Virtual Proxy when I want to defer the creation of expensive objects until they’re actually needed - like loading images only when displayed. I use Caching Proxy when I want to cache the results of expensive operations - like caching database query results to avoid repeated queries.”

Q: “How does Proxy Pattern relate to SOLID principles?”

A:

“Proxy Pattern supports the Single Responsibility Principle by separating access control, caching, or lazy loading into the proxy. It supports the Open/Closed Principle - you can add functionality (proxy) without modifying the real object. It supports the Dependency Inversion Principle by having clients depend on the Subject interface rather than concrete implementations.”

Before your interview, make sure you can:

  • Define Proxy Pattern clearly in one sentence
  • Explain when to use it (with examples showing lazy loading/caching)
  • Describe Proxy vs Decorator Pattern
  • Implement Proxy Pattern from scratch
  • Compare with other structural patterns (Decorator, Adapter)
  • List proxy variants (Virtual, Protection, Remote, Caching)
  • List benefits and trade-offs
  • Identify common mistakes (not delegating, different interface)
  • Give 2-3 real-world examples
  • Connect to SOLID principles
  • Discuss when NOT to use it

Remember: Proxy Pattern is about controlling access to objects - providing a placeholder that adds functionality like lazy loading, caching, or access control! 🎯