Loading content...
The Proxy pattern isn't just an academic exercise—it's one of the most widely used design patterns in production software. From the database connection pool you use every day to the JavaScript proxies that power Vue.js reactivity, proxies are everywhere.
In this final page, we'll explore real-world applications across multiple domains, examine framework implementations that leverage proxies, and synthesize everything into practical guidance for your own systems.
By the end of this page, you'll recognize proxy patterns in existing systems, understand how major frameworks leverage proxies, and have concrete templates for implementing proxies in common scenarios: image galleries, API gateways, ORM lazy loading, and more.
Scenario: A photo management application displays thousands of images in a gallery view. Users can browse, search, preview, and open images for editing.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
// Complete Image Gallery implementation with Virtual Proxy // ===== INTERFACES ===== interface GalleryImage { readonly id: string; readonly filename: string; // Lightweight operations (should work without loading full image) getMetadata(): ImageMetadata; getThumbnail(): Promise<ImageBitmap>; getPreview(): Promise<ImageBitmap>; // Medium resolution // Heavy operations (require full image) getFullResolution(): Promise<ImageBitmap>; exportAs(format: ExportFormat, quality: number): Promise<Blob>; getHistogram(): ColorHistogram; applyFilter(filter: ImageFilter): Promise<GalleryImage>;} interface ImageMetadata { filename: string; width: number; height: number; fileSize: number; format: string; capturedAt?: Date; camera?: string; location?: GeoLocation; tags: string[];} // ===== REAL SUBJECT ===== class RealGalleryImage implements GalleryImage { private fullImage: ImageBitmap | null = null; private metadata: ImageMetadata; constructor( public readonly id: string, public readonly filename: string, private readonly imagePath: string ) { // Even RealGalleryImage loads metadata lazily for efficiency this.metadata = this.loadMetadata(); } private loadMetadata(): ImageMetadata { // Read EXIF/metadata from file header (fast) return ImageMetadataReader.read(this.imagePath); } private async loadFullImage(): Promise<ImageBitmap> { if (!this.fullImage) { console.log(`[RealImage] Loading full resolution: ${this.filename}`); const blob = await FileSystem.readBlob(this.imagePath); this.fullImage = await createImageBitmap(blob); } return this.fullImage; } getMetadata(): ImageMetadata { return this.metadata; } async getThumbnail(): Promise<ImageBitmap> { // Check for pre-generated thumbnail const thumbPath = `${this.imagePath}.thumb.jpg`; if (await FileSystem.exists(thumbPath)) { const blob = await FileSystem.readBlob(thumbPath); return createImageBitmap(blob); } // Generate from full image const full = await this.loadFullImage(); return this.resizeImage(full, 150, 150); } async getPreview(): Promise<ImageBitmap> { const full = await this.loadFullImage(); return this.resizeImage(full, 800, 600); } async getFullResolution(): Promise<ImageBitmap> { return this.loadFullImage(); } async exportAs(format: ExportFormat, quality: number): Promise<Blob> { const full = await this.loadFullImage(); return ImageEncoder.encode(full, format, quality); } getHistogram(): ColorHistogram { if (!this.fullImage) { throw new Error('Must load full image first'); } return ColorAnalyzer.computeHistogram(this.fullImage); } async applyFilter(filter: ImageFilter): Promise<GalleryImage> { const full = await this.loadFullImage(); const filtered = await filter.apply(full); const newPath = await this.saveFiltered(filtered); return new RealGalleryImage(generateId(), `filtered_${this.filename}`, newPath); } private async resizeImage( source: ImageBitmap, maxWidth: number, maxHeight: number ): Promise<ImageBitmap> { const canvas = new OffscreenCanvas(maxWidth, maxHeight); const ctx = canvas.getContext('2d')!; // Calculate aspect-ratio preserving dimensions const scale = Math.min(maxWidth / source.width, maxHeight / source.height); const width = source.width * scale; const height = source.height * scale; ctx.drawImage(source, 0, 0, width, height); return createImageBitmap(canvas); }}Now the Virtual Proxy that makes the gallery performant:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
// ===== VIRTUAL PROXY ===== class GalleryImageProxy implements GalleryImage { // Lightweight data loaded immediately private metadataCache: ImageMetadata | null = null; private thumbnailCache: ImageBitmap | null = null; // Heavy resources created on demand private realImage: RealGalleryImage | null = null; private loadingPromise: Promise<RealGalleryImage> | null = null; constructor( public readonly id: string, public readonly filename: string, private readonly imagePath: string ) { // Proxy is extremely cheap - just stores paths } // ===== LIGHTWEIGHT OPERATIONS ===== getMetadata(): ImageMetadata { if (!this.metadataCache) { // Read just the file header - always fast this.metadataCache = ImageMetadataReader.read(this.imagePath); } return this.metadataCache; } async getThumbnail(): Promise<ImageBitmap> { if (!this.thumbnailCache) { // Try pre-generated thumbnail first const thumbPath = `${this.imagePath}.thumb.jpg`; if (await FileSystem.exists(thumbPath)) { const blob = await FileSystem.readBlob(thumbPath); this.thumbnailCache = await createImageBitmap(blob); } else { // Generate thumbnail without loading full RealImage this.thumbnailCache = await ThumbnailGenerator.generate( this.imagePath, 150, 150 ); } } return this.thumbnailCache; } async getPreview(): Promise<ImageBitmap> { // Preview might not need full resolution // Try medium-res cache first const previewPath = `${this.imagePath}.preview.jpg`; if (await FileSystem.exists(previewPath)) { const blob = await FileSystem.readBlob(previewPath); return createImageBitmap(blob); } // Fall back to loading via real image return this.ensureRealImage().then(real => real.getPreview()); } // ===== HEAVY OPERATIONS ===== private async ensureRealImage(): Promise<RealGalleryImage> { if (this.realImage) { return this.realImage; } // Prevent duplicate loading if (this.loadingPromise) { return this.loadingPromise; } console.log(`[Proxy] Creating RealGalleryImage for ${this.filename}`); this.loadingPromise = new Promise(resolve => { // Small delay to allow UI updates requestIdleCallback(() => { this.realImage = new RealGalleryImage( this.id, this.filename, this.imagePath ); resolve(this.realImage); }); }); return this.loadingPromise; } async getFullResolution(): Promise<ImageBitmap> { const real = await this.ensureRealImage(); return real.getFullResolution(); } async exportAs(format: ExportFormat, quality: number): Promise<Blob> { const real = await this.ensureRealImage(); return real.exportAs(format, quality); } getHistogram(): ColorHistogram { if (!this.realImage) { throw new Error('Must load full image first. Call getFullResolution()'); } return this.realImage.getHistogram(); } async applyFilter(filter: ImageFilter): Promise<GalleryImage> { const real = await this.ensureRealImage(); return real.applyFilter(filter); } // ===== PROXY-SPECIFIC METHODS ===== isLoaded(): boolean { return this.realImage !== null; } unload(): void { // Release memory - can be reloaded later this.realImage = null; this.loadingPromise = null; // Keep thumbnails - they're small } async preload(): Promise<void> { await this.ensureRealImage(); }} // ===== GALLERY USING PROXIES ===== class PhotoGallery { private images: GalleryImage[] = []; private visibleRange = { start: 0, end: 50 }; async loadLibrary(libraryPath: string): Promise<void> { const files = await FileSystem.listImages(libraryPath); console.log(`Loading library with ${files.length} images...`); const start = performance.now(); // Create proxies for ALL images - instant! this.images = files.map(file => new GalleryImageProxy( file.id, file.name, file.path ) ); console.log(`Created ${this.images.length} proxies in ${performance.now() - start}ms`); // Output: "Created 50000 proxies in 45ms" } getImagesForDisplay(): GalleryImage[] { return this.images.slice(this.visibleRange.start, this.visibleRange.end); } async renderThumbnails(container: HTMLElement): Promise<void> { const visible = this.getImagesForDisplay(); for (const image of visible) { const thumbContainer = document.createElement('div'); thumbContainer.className = 'thumbnail'; // Only loads thumbnail - not full image! const thumbnail = await image.getThumbnail(); const canvas = document.createElement('canvas'); canvas.width = thumbnail.width; canvas.height = thumbnail.height; canvas.getContext('2d')!.drawImage(thumbnail, 0, 0); thumbContainer.appendChild(canvas); thumbContainer.onclick = () => this.openImage(image); container.appendChild(thumbContainer); } } async openImage(image: GalleryImage): Promise<void> { // NOW we load the full resolution console.log(`Opening ${image.filename}...`); const fullRes = await image.getFullResolution(); this.displayFullscreen(fullRes); } onScroll(scrollPosition: number): void { const newStart = Math.floor(scrollPosition / 100) * 50; const oldRange = this.visibleRange; this.visibleRange = { start: newStart, end: newStart + 50 }; // Unload images that scrolled out of view for (let i = oldRange.start; i < oldRange.end; i++) { if (i < this.visibleRange.start || i >= this.visibleRange.end) { const img = this.images[i] as GalleryImageProxy; img.unload(); } } }}With proxies: 50,000 images load in ~50ms (just proxy creation). Without proxies: 50,000 images would take 500GB+ RAM and hours to load. The gallery is instantly responsive, with images loading on-demand as users scroll or click.
Scenario: A microservices architecture serves a mobile app and web frontend. Multiple backend services need to be accessed, each with different authentication, rate limiting, and caching needs.
An API Gateway is essentially a composite proxy that:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
// API Gateway Implementation - Composite Proxy Pattern interface ServiceRequest { path: string; method: 'GET' | 'POST' | 'PUT' | 'DELETE'; headers: Record<string, string>; body?: unknown; query?: Record<string, string>;} interface ServiceResponse { status: number; headers: Record<string, string>; body: unknown;} // Gateway combines multiple proxy patternsclass APIGateway { private rateLimiter: RateLimiter; private cache: ResponseCache; private circuitBreakers: Map<string, CircuitBreaker>; constructor( private readonly serviceRegistry: ServiceRegistry, private readonly authService: AuthService, config: GatewayConfig ) { this.rateLimiter = new RateLimiter(config.rateLimit); this.cache = new ResponseCache(config.cache); this.circuitBreakers = new Map(); } async handleRequest(request: ServiceRequest): Promise<ServiceResponse> { const requestId = generateRequestId(); const startTime = performance.now(); try { // ===== 1. AUTHENTICATION (Protection Proxy) ===== const authResult = await this.authenticate(request); if (!authResult.authenticated) { return this.errorResponse(401, 'Unauthorized'); } // ===== 2. RATE LIMITING (Protection Proxy) ===== const rateLimitResult = await this.rateLimiter.check( authResult.user.id, request.path ); if (!rateLimitResult.allowed) { return this.errorResponse(429, 'Rate limit exceeded', { 'Retry-After': String(rateLimitResult.retryAfter), 'X-RateLimit-Limit': String(rateLimitResult.limit), 'X-RateLimit-Remaining': '0', }); } // ===== 3. CACHE CHECK (Smart Reference) ===== if (request.method === 'GET') { const cached = await this.cache.get(request); if (cached) { this.logRequest(requestId, request, 'CACHE_HIT', startTime); return { ...cached, headers: { ...cached.headers, 'X-Cache': 'HIT', }, }; } } // ===== 4. ROUTE TO SERVICE (Remote Proxy) ===== const service = this.resolveService(request.path); const circuitBreaker = this.getCircuitBreaker(service.name); if (circuitBreaker.isOpen()) { return this.errorResponse(503, `Service ${service.name} unavailable`); } // Forward request to backend service const response = await this.forwardRequest(service, request, authResult); // ===== 5. CACHE RESPONSE ===== if (request.method === 'GET' && response.status === 200) { await this.cache.set(request, response); } circuitBreaker.recordSuccess(); this.logRequest(requestId, request, 'SUCCESS', startTime); return response; } catch (error) { this.logRequest(requestId, request, 'ERROR', startTime, error as Error); if (error instanceof ServiceError) { const service = this.resolveService(request.path); this.getCircuitBreaker(service.name).recordFailure(); } return this.errorResponse(500, 'Internal server error'); } } private async authenticate(request: ServiceRequest): Promise<AuthResult> { const token = request.headers['authorization']?.replace('Bearer ', ''); if (!token) { return { authenticated: false }; } try { const user = await this.authService.validateToken(token); return { authenticated: true, user }; } catch { return { authenticated: false }; } } private resolveService(path: string): ServiceEndpoint { // /api/users/* -> User Service // /api/products/* -> Product Service // etc. const [, , serviceName] = path.split('/'); return this.serviceRegistry.resolve(serviceName); } private getCircuitBreaker(serviceName: string): CircuitBreaker { if (!this.circuitBreakers.has(serviceName)) { this.circuitBreakers.set( serviceName, new CircuitBreaker({ failureThreshold: 5, resetTimeout: 30000 }) ); } return this.circuitBreakers.get(serviceName)!; } private async forwardRequest( service: ServiceEndpoint, request: ServiceRequest, auth: AuthResult ): Promise<ServiceResponse> { // Transform path for backend const backendPath = request.path.replace(`/api/${service.name}`, ''); const response = await fetch(`${service.url}${backendPath}`, { method: request.method, headers: { ...request.headers, 'X-User-Id': auth.user?.id ?? '', 'X-Request-Id': generateRequestId(), }, body: request.body ? JSON.stringify(request.body) : undefined, }); return { status: response.status, headers: Object.fromEntries(response.headers.entries()), body: await response.json(), }; } private errorResponse( status: number, message: string, headers: Record<string, string> = {} ): ServiceResponse { return { status, headers: { 'Content-Type': 'application/json', ...headers, }, body: { error: message }, }; }}Production API gateways like Kong, AWS API Gateway, and Envoy implement exactly these proxy patterns. They add authentication, rate limiting, caching, circuit breaking, and request routing—all as proxy layers between clients and backend services.
Scenario: An Object-Relational Mapping (ORM) framework maps database tables to objects. Related entities (like a User's Posts) shouldn't be loaded until accessed.
When you load a User from the database, related collections like posts aren't eagerly loaded. Instead, the ORM creates proxy objects that load data on first access.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
// Simplified ORM Lazy Loading Implementation // Entity definitionsinterface User { id: string; name: string; email: string; posts: Post[]; // Lazy loaded profile: UserProfile; // Lazy loaded} interface Post { id: string; title: string; content: string; author: User; // Lazy loaded back-reference comments: Comment[]; // Lazy loaded} // Lazy Collection Proxyclass LazyCollection<T> implements Iterable<T> { private items: T[] | null = null; private isLoading = false; constructor( private readonly loader: () => Promise<T[]> ) {} private ensureLoaded(): T[] { if (this.items === null) { throw new Error( 'Collection not loaded. Use await collection.load() or access asynchronously.' ); } return this.items; } async load(): Promise<T[]> { if (this.items !== null) { return this.items; } if (this.isLoading) { // Wait for ongoing load while (this.isLoading) { await new Promise(r => setTimeout(r, 10)); } return this.items!; } this.isLoading = true; try { console.log('[LazyCollection] Loading items...'); this.items = await this.loader(); console.log(`[LazyCollection] Loaded ${this.items.length} items`); return this.items; } finally { this.isLoading = false; } } get isLoaded(): boolean { return this.items !== null; } get length(): number { return this.ensureLoaded().length; } [Symbol.iterator](): Iterator<T> { return this.ensureLoaded()[Symbol.iterator](); } map<U>(fn: (item: T) => U): U[] { return this.ensureLoaded().map(fn); } filter(fn: (item: T) => boolean): T[] { return this.ensureLoaded().filter(fn); } find(fn: (item: T) => boolean): T | undefined { return this.ensureLoaded().find(fn); }} // Lazy Entity Proxy for single relationsclass LazyEntity<T> { private entity: T | null = null; private isLoading = false; constructor( private readonly loader: () => Promise<T> ) {} async get(): Promise<T> { if (this.entity !== null) { return this.entity; } if (this.isLoading) { while (this.isLoading) { await new Promise(r => setTimeout(r, 10)); } return this.entity!; } this.isLoading = true; try { console.log('[LazyEntity] Loading entity...'); this.entity = await this.loader(); return this.entity; } finally { this.isLoading = false; } } get isLoaded(): boolean { return this.entity !== null; }} // ORM that creates proxiesclass SimpleORM { constructor(private readonly db: Database) {} async findUser(id: string): Promise<User> { // Load only the user's direct properties const row = await this.db.query( 'SELECT id, name, email FROM users WHERE id = ?', [id] ); // Create user with lazy proxies for relations const user: User = { id: row.id, name: row.name, email: row.email, // Posts are NOT loaded yet - just a proxy posts: new LazyCollection<Post>(async () => { const postRows = await this.db.query( 'SELECT * FROM posts WHERE author_id = ?', [id] ); return postRows.map(row => this.hydratePost(row, user)); }) as unknown as Post[], // Profile is NOT loaded yet - just a proxy profile: new LazyEntity<UserProfile>(async () => { const profileRow = await this.db.query( 'SELECT * FROM user_profiles WHERE user_id = ?', [id] ); return this.hydrateProfile(profileRow); }) as unknown as UserProfile, }; return user; } private hydratePost(row: any, author: User): Post { return { id: row.id, title: row.title, content: row.content, author, // Already loaded, no proxy needed // Comments lazy loaded comments: new LazyCollection<Comment>(async () => { const commentRows = await this.db.query( 'SELECT * FROM comments WHERE post_id = ?', [row.id] ); return commentRows.map(r => this.hydrateComment(r)); }) as unknown as Comment[], }; }} // Usage demonstrates lazy loadingasync function example() { const orm = new SimpleORM(database); // Only loads user data - 1 query const user = await orm.findUser('user-123'); console.log(user.name); // Works immediately // Posts NOT loaded yet console.log((user.posts as unknown as LazyCollection<Post>).isLoaded); // false // This triggers loading - 1 more query await (user.posts as unknown as LazyCollection<Post>).load(); console.log(user.posts.length); // Now works // Iterate (already loaded, no more queries) for (const post of user.posts) { console.log(post.title); // Comments for this post NOT loaded yet // Only loads when accessed await (post.comments as unknown as LazyCollection<Comment>).load(); }}Lazy loading can cause the 'N+1 query problem' - 1 query for the main entity, then N queries for each related entity. ORMs provide 'eager loading' options to fetch relations upfront when you know you'll need them. The proxy pattern enables both strategies.
Scenario: Database connections are expensive to create (TCP handshake, authentication, resource allocation). Applications need many concurrent connections but shouldn't create a new one for every query.
A connection pool is a virtual proxy that manages a pool of real connections, providing them on-demand and returning them for reuse.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
// Database Connection Pool Implementation interface DatabaseConnection { query(sql: string, params?: unknown[]): Promise<QueryResult>; beginTransaction(): Promise<Transaction>; close(): Promise<void>; isHealthy(): boolean;} interface PooledConnection extends DatabaseConnection { release(): void; readonly poolId: string; readonly createdAt: Date; readonly lastUsedAt: Date;} // Connection Pool - manages real connections as a proxyclass ConnectionPool { private availableConnections: RealDatabaseConnection[] = []; private inUseConnections: Set<RealDatabaseConnection> = new Set(); private waitQueue: Array<{ resolve: (conn: PooledConnection) => void; reject: (error: Error) => void; timeout: NodeJS.Timeout; }> = []; private readonly config: PoolConfig; private isShuttingDown = false; constructor( private readonly connectionFactory: ConnectionFactory, config: Partial<PoolConfig> = {} ) { this.config = { minConnections: config.minConnections ?? 5, maxConnections: config.maxConnections ?? 20, acquireTimeout: config.acquireTimeout ?? 30000, idleTimeout: config.idleTimeout ?? 60000, healthCheckInterval: config.healthCheckInterval ?? 30000, }; this.initialize(); } private async initialize(): Promise<void> { // Create minimum connections console.log(`[Pool] Initializing with ${this.config.minConnections} connections`); const promises = []; for (let i = 0; i < this.config.minConnections; i++) { promises.push(this.createConnection()); } const connections = await Promise.all(promises); this.availableConnections.push(...connections); // Start health checker this.startHealthChecker(); } // Get a connection from the pool (Virtual Proxy behavior) async acquire(): Promise<PooledConnection> { if (this.isShuttingDown) { throw new Error('Pool is shutting down'); } // Try to get an available connection const connection = this.availableConnections.pop(); if (connection) { if (connection.isHealthy()) { return this.wrapConnection(connection); } else { // Connection died, create new one await connection.close(); return this.acquire(); // Retry } } // No available connections const totalConnections = this.availableConnections.length + this.inUseConnections.size; if (totalConnections < this.config.maxConnections) { // Can create new connection console.log('[Pool] Creating new connection'); const newConnection = await this.createConnection(); return this.wrapConnection(newConnection); } // At max capacity - wait for a connection to be released return this.waitForConnection(); } // Wrap real connection with pool management private wrapConnection(real: RealDatabaseConnection): PooledConnection { this.inUseConnections.add(real); const self = this; const wrapper: PooledConnection = { poolId: real.id, createdAt: real.createdAt, lastUsedAt: new Date(), async query(sql: string, params?: unknown[]): Promise<QueryResult> { return real.query(sql, params); }, async beginTransaction(): Promise<Transaction> { return real.beginTransaction(); }, async close(): Promise<void> { // Don't actually close - release back to pool wrapper.release(); }, isHealthy(): boolean { return real.isHealthy(); }, release(): void { self.releaseConnection(real); }, }; return wrapper; } // Return connection to pool private releaseConnection(connection: RealDatabaseConnection): void { this.inUseConnections.delete(connection); // Check if anyone is waiting if (this.waitQueue.length > 0) { const waiter = this.waitQueue.shift()!; clearTimeout(waiter.timeout); waiter.resolve(this.wrapConnection(connection)); return; } // Return to available pool if (connection.isHealthy()) { this.availableConnections.push(connection); } else { // Connection died, discard it connection.close(); } } private waitForConnection(): Promise<PooledConnection> { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { const index = this.waitQueue.findIndex(w => w.resolve === resolve); if (index !== -1) { this.waitQueue.splice(index, 1); } reject(new Error('Connection acquire timeout')); }, this.config.acquireTimeout); this.waitQueue.push({ resolve, reject, timeout }); }); } private async createConnection(): Promise<RealDatabaseConnection> { return this.connectionFactory.create(); } private startHealthChecker(): void { setInterval(async () => { // Check and remove dead connections const connectionsToCheck = [...this.availableConnections]; for (const conn of connectionsToCheck) { if (!conn.isHealthy()) { const index = this.availableConnections.indexOf(conn); if (index !== -1) { this.availableConnections.splice(index, 1); await conn.close(); // Replace with new connection if below minimum if (this.availableConnections.length < this.config.minConnections) { const newConn = await this.createConnection(); this.availableConnections.push(newConn); } } } } }, this.config.healthCheckInterval); } // Graceful shutdown async shutdown(): Promise<void> { this.isShuttingDown = true; // Reject all waiters for (const waiter of this.waitQueue) { clearTimeout(waiter.timeout); waiter.reject(new Error('Pool is shutting down')); } this.waitQueue = []; // Wait for in-use connections to be released while (this.inUseConnections.size > 0) { await new Promise(r => setTimeout(r, 100)); } // Close all connections for (const conn of this.availableConnections) { await conn.close(); } console.log('[Pool] Shutdown complete'); } getStats(): PoolStats { return { available: this.availableConnections.length, inUse: this.inUseConnections.size, waiting: this.waitQueue.length, total: this.availableConnections.length + this.inUseConnections.size, }; }}Many popular frameworks and libraries leverage the Proxy pattern. Understanding these implementations deepens your appreciation for the pattern's versatility.
Vue 3's Reactivity System is built entirely on JavaScript Proxy. When you create reactive data, Vue wraps it in a proxy that tracks access and triggers updates.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
// Simplified Vue-style reactivity using Proxyfunction reactive<T extends object>(target: T): T { const handlers: Set<() => void> = new Set(); let currentEffect: (() => void) | null = null; return new Proxy(target, { get(obj, prop, receiver) { // Track which effect is reading this property if (currentEffect) { handlers.add(currentEffect); } const value = Reflect.get(obj, prop, receiver); // Recursively make nested objects reactive if (typeof value === 'object' && value !== null) { return reactive(value); } return value; }, set(obj, prop, value, receiver) { const result = Reflect.set(obj, prop, value, receiver); // Trigger all effects that depend on this property handlers.forEach(effect => effect()); return result; } });} // Usage mirrors Vue's Composition APIconst state = reactive({ count: 0, user: { name: 'Alice' }}); // When count changes, this re-runs automaticallywatchEffect(() => { console.log(`Count is: ${state.count}`);}); state.count++; // Logs: "Count is: 1"state.count++; // Logs: "Count is: 2"Common mistakes: (1) Over-engineering with layers of proxies when simpler solutions exist, (2) Breaking object identity (proxy !== realObject), (3) Performance overhead for high-frequency operations, (4) Confusion during debugging when behavior comes from invisible proxies. Use proxies deliberately, not by default.
Congratulations! You've completed a comprehensive study of the Proxy pattern—one of the most versatile and widely-used design patterns in software engineering.
| Problem | Proxy Type | Solution |
|---|---|---|
| Expensive object creation | Virtual Proxy | Lazy initialization on first access |
| Security/authorization needed | Protection Proxy | Permission checks before delegation |
| Object on remote machine | Remote Proxy | Network abstraction with error handling |
| Need logging/caching/metrics | Smart Reference | Intercept and enhance operations |
| Multiple concerns combined | Composite | Stack multiple proxy layers |
You've mastered the Proxy pattern with deep implementations and real-world applications. You can now recognize proxy opportunities in your systems and implement production-quality proxies for lazy loading, access control, remote communication, and cross-cutting concerns. The proxy is your tool for controlling access without compromising design clarity.
Next Steps:
With the Proxy pattern complete, you've now covered all seven structural patterns. In the next chapter, we'll explore Behavioral Patterns—patterns that define how objects communicate and distribute responsibilities. Starting with the Strategy pattern, you'll learn to encapsulate algorithms and make them interchangeable at runtime.