Coding · Design patterns
Proxy pattern explained with examples
Short answer
Proxy is a structural design pattern that provides a substitute object with the same interface as a real object, so that the proxy can control access to it. The client talks to the proxy as if it were the real object, and the proxy decides when and how to forward each call. Common kinds are virtual proxies (lazy creation), protection proxies (access checks), caching proxies and remote proxies.
Intent and the problem it solves
Intent: provide a surrogate or placeholder for another object to control access to it. Proxy is one of the structural patterns in the Gang of Four (GoF) book.
Suppose an app calls a weather service over the network. Each call takes hundreds of milliseconds and costs money, and the same city is often requested many times per minute. You cannot change the service class, and you do not want to add caching code to every place that calls it. A proxy solves this: it implements the same interface, keeps a cache, and only calls the real service on a cache miss. Callers do not change at all.
The same idea covers other needs: create an expensive object only when it is first used, check permissions before a call, log every call, or hide the fact that the real object lives on another machine.
Structure and participants
| Role | Responsibility |
|---|---|
| Subject | The interface shared by the real object and the proxy, so the client can use either one |
| Real subject | The object that does the actual work |
| Proxy | Implements the subject interface, holds or creates a reference to the real subject, and adds control logic before or after forwarding |
| Client | Works only with the subject interface and does not know whether it has a proxy |
| Kind of proxy | What it controls |
|---|---|
| Virtual proxy | Delays creating an expensive object until it is needed |
| Protection proxy | Checks whether the caller is allowed to perform an operation |
| Caching proxy | Stores results and returns them for repeated requests |
| Remote proxy | Represents an object in another process or machine and handles the communication |
| Logging proxy | Records calls and arguments without changing the real object |
TypeScript implementation: a caching proxy
WeatherService is the subject. CachedWeatherService is the proxy: it has the same method and forwards to the real service only when the cached value is missing or too old.
interface WeatherService {
getTemperature(city: string): Promise<number>;
}
class RemoteWeatherService implements WeatherService {
calls = 0;
async getTemperature(city: string): Promise<number> {
this.calls++; // imagine a slow HTTP request here
return city.length * 3;
}
}
class CachedWeatherService implements WeatherService {
private cache = new Map<string, { value: number; time: number }>();
constructor(private real: WeatherService, private maxAgeMs = 60_000) {}
async getTemperature(city: string): Promise<number> {
const hit = this.cache.get(city);
if (hit && Date.now() - hit.time < this.maxAgeMs) return hit.value;
const value = await this.real.getTemperature(city);
this.cache.set(city, { value, time: Date.now() });
return value;
}
}
async function main() {
const real = new RemoteWeatherService();
const weather: WeatherService = new CachedWeatherService(real);
await weather.getTemperature("Paris");
await weather.getTemperature("Paris");
console.log(await weather.getTemperature("Hanoi"), "real calls:", real.calls);
// 15 real calls: 2
}
main();The client variable is typed as WeatherService, so you can switch between the real service and the proxy in one place, for example in a dependency injection setup.
A virtual and protection proxy with JavaScript Proxy
JavaScript has a built-in Proxy object (ES2015) that intercepts operations such as property reads, writes and function calls through handler functions called traps. It is a convenient way to build a proxy without writing a class for each interface.
type Account = { owner: string; balance: number };
function readOnly<T extends object>(target: T): T {
return new Proxy(target, {
set(_obj, prop) {
throw new Error("Cannot change " + String(prop) + ": read-only");
},
});
}
const account = readOnly<Account>({ owner: "Ana", balance: 100 });
console.log(account.balance); // 100
try {
account.balance = 0;
} catch (e) {
console.log((e as Error).message); // Cannot change balance: read-only
}When to use it and when not to
- Use it to add lazy loading, access control, caching, logging or remote access without changing the real class or its callers.
- Use it when the real object is outside your control, such as a library class or a network client.
- Do not use it when a plain function wrapper is enough. For one function, a higher-order function such as
memoize(fn)is simpler than a class. - Do not hide slow or failing remote calls behind a proxy that looks local. Callers still need to handle latency and errors, so make that visible in the interface (for example by returning a
Promise). - Be careful with identity: a proxy is a different object from the real subject, so
proxy === realis false.
Real-world uses
- Vue 3 builds its reactivity system on JavaScript
Proxy: reading a property records a dependency and writing it triggers updates. - Java provides
java.lang.reflect.Proxyto create dynamic proxy classes at runtime; frameworks use it to add transactions, security checks or remote calls around interfaces. - Object-relational mappers such as Hibernate return lazy proxies for related entities and load the real data from the database on first access.
- Remote procedure call libraries generate client stubs that look like local objects but send each call over the network.
Related patterns
| Pattern | How it differs from Proxy |
|---|---|
| Decorator | Same structure, different goal: Decorator adds new behavior and is often stacked; Proxy controls access and usually manages the real object's life cycle. |
| Adapter | Adapter gives an object a different interface; Proxy keeps exactly the same interface. |
| Facade | Facade offers a new, simpler interface to a whole subsystem; Proxy stands in for one object. |
| Flyweight | Flyweight shares objects to save memory; a virtual proxy delays creating an object to save time and memory. |
FAQ
What is the difference between Proxy and Decorator?
Both wrap an object with the same interface. A decorator adds responsibilities and is usually composed by the client, while a proxy controls access to the real object and often creates or manages that object itself.
Is the JavaScript Proxy object the same as the Proxy pattern?
It is a language feature that makes the pattern easy to implement. A JavaScript Proxy intercepts operations on a target object through traps, which is exactly what a proxy in the GoF sense does.
What is a virtual proxy?
A virtual proxy stands in for an object that is expensive to create, such as a large image. It creates the real object only when a method that needs it is first called.
Is a reverse proxy server an example of the Proxy pattern?
It follows the same idea at the network level: a server such as nginx sits in front of other servers with the same HTTP interface and adds caching, access control or load balancing. The GoF pattern describes the same idea for objects in one program.