Structural PatternsProxy PatternHard⏱️ ~3 min

Proxy Pattern: Interview Questions and Machine Coding Considerations

Common Interview Questions:

Question 1: How would you implement thread-safe lazy initialization in a Virtual Proxy?

BookProxy.ensureLoaded():
  if realBook is null:
    synchronized(lock):
      if realBook is null: // Double-checked locking
        realBook = new RealBook(filePath)
  return realBook

Key Points: Double-checked locking avoids synchronization overhead on every access after initialization. First check is unsynchronized (fast path). Synchronized block entered only when null. Second check inside ensures only one thread creates the object if multiple threads passed the first check simultaneously. In languages with volatile semantics (Java, C#), mark the reference volatile to prevent instruction reordering issues. Alternative: use lazy initialization holder pattern (static inner class in Java) which is thread-safe by the language specification without explicit locking.

Question 2: Can you chain multiple Proxies? Give an example where this makes sense.

Answer: Yes, Proxies can be chained because each Proxy implements the Subject interface. Example: Client → LoggingProxy → CacheProxy → ProtectionProxy → RealSubject. The LoggingProxy logs all requests. The CacheProxy checks if the result is cached before delegating. The ProtectionProxy validates permissions before allowing the operation. Each Proxy is independent and reusable. However, excessive chaining increases complexity and makes debugging difficult. If you find yourself chaining more than three Proxies, consider whether a single Proxy with composed strategies (Strategy Pattern for access control, caching, logging) would be clearer. The trade-off is flexibility (chaining allows mix-and-match) versus simplicity (single Proxy with configuration is easier to understand).

Question 3: What is the difference between Proxy and Adapter in terms of intent and structure?

Proxy
Intent: Control access to an object
Interface: Same as the subject
Relationship: Proxy IS-A Subject (implements same interface)
Delegation: To the real subject
Example: ImageProxy implements Image, controls when RealImage loads
Adapter
Intent: Make incompatible interfaces work together
Interface: Different from the adaptee
Relationship: Adapter IS-A Target (implements target interface)
Delegation: To the adaptee (incompatible object)
Example: ShapeAdapter implements Shape, adapts LegacyRectangle (different interface)

If the wrapped object already implements the interface you need, and you are adding control logic, it is a Proxy. If you are converting one interface to another, it is an Adapter. However, a Protection Proxy that wraps a third-party object and also translates its interface combines both patterns (this is acceptable when pragmatism trumps purity).

Machine Coding Considerations:

  • Handle null references: In Virtual Proxy, always check if realSubject is null before delegating. Fail gracefully if initialization fails (throw descriptive exception or return default/null object). Do not let NullPointerException propagate without context.
  • Proxy identity vs equality: If clients compare objects using == (reference equality), Proxy and RealSubject are different objects. Override equals() and hashCode() to delegate to RealSubject if semantic equality is needed. Alternatively, document that clients must use the Proxy reference consistently.
  • Lifecycle management: If RealSubject is expensive or holds resources (file handles, connections), the Proxy should provide cleanup methods. Example: dispose() or close() that releases the RealSubject. Make Proxy implement AutoCloseable (Java) or IDisposable (C#) if appropriate. If RealSubject was never created (lazy load not triggered), cleanup is a no-op.
  • Logging and observability: Add timestamps, user context, and operation names to Proxy logs. This helps trace which operations were proxied and which went directly to RealSubject (if both are accessible). Use structured logging (JSON) for easier parsing in production.
  • Testing strategy: Create a MockSubject that implements the Subject interface for unit testing the Proxy logic without expensive RealSubject initialization. Test Proxy separately: verify access control triggers, lazy initialization happens once, caching works correctly. Test RealSubject separately: verify business logic. Integration test: verify Proxy and RealSubject work together correctly.
Interview Tip: When live-coding a Proxy, start with the Subject interface, then RealSubject (simple implementation), then Proxy (add one concern at a time: lazy load, then access check, then logging). This incremental approach shows structured thinking. Do not try to implement everything at once. Explain trade-offs as you go: "I am using double-checked locking here, but in production I would prefer a lazy holder pattern for simplicity if the language supports it."

Common Mistakes to Avoid:

Mistake: Creating Proxy without extracting a Subject interface. RealSubject is a concrete class, and Proxy subclasses it.
Problem: Violates Liskov Substitution Principle if Proxy overrides methods and changes behavior unexpectedly. Also prevents using multiple Proxies for the same RealSubject (cannot have multiple inheritance).
Fix: Always extract an interface or abstract class that both Proxy and RealSubject implement.

Mistake: Proxy holds business logic instead of just control logic.
Problem: Violates Single Responsibility Principle. Proxy becomes a "god object" that duplicates RealSubject logic or implements unrelated features.
Fix: Proxy should only add control concerns (access, lifecycle, logging). Business logic belongs in RealSubject or should be delegated to separate strategy objects.

Mistake: Forgetting to check if RealSubject is already initialized before creating it again in Virtual Proxy.
Problem: Multiple RealSubject instances are created, wasting resources and causing inconsistent state (each instance may cache different data).
Fix: Use a boolean flag (isInitialized) or check if reference is null before initializing.
💡 Key Takeaways
Double-checked locking ensures thread-safe lazy initialization without synchronization overhead on every access
Proxies can be chained for composing concerns, but excessive chaining increases complexity
Proxy and Adapter differ in intent: Proxy controls access with same interface, Adapter converts between interfaces
Handle null references, implement cleanup methods, and override equals/hashCode for correct Proxy behavior
Test Proxy logic separately from RealSubject using mock implementations
📌 Examples
1Virtual Proxy with thread-safe lazy load using double-checked locking
2Chained Proxies: Logging → Cache → Protection → RealSubject for composable concerns
3Protection Proxy that checks user roles before allowing method execution, throwing AccessDeniedException if unauthorized
← Back to Proxy Pattern Overview
Proxy Pattern: Interview Questions and Machine Coding Considerations | Proxy Pattern - System Overflow