Beyond a single class, where does the Proxy pattern show up at the architecture level, and when is adding a proxy the wrong call?
answer
- Gateway, reverse proxy, sidecar, DB proxy = Proxy at scale
- Sidecar = smart-reference proxy in its own process
- Chokepoint: uniform policy vs single failure domain
- Retry amplification across stacked proxies
- No policy = pure indirection, don't add it
basics
~20 sThe same idea scales up: API gateways, reverse proxies, service-mesh sidecars, caching layers, and database connection proxies all stand in front of something and control access to it. It's the wrong call when it adds a hop and an operating cost without policy — pure pass-through indirection.
solid answer
~60 sAt architecture scale a proxy is any component that sits in the request path presenting the target's interface and applying policy: an **API gateway** (authn, rate limiting, routing, request shaping), a **reverse proxy/CDN** (TLS termination, caching, compression), a **sidecar in a service mesh** (mTLS, retries, timeouts, circuit breaking, telemetry — a remote proxy plus smart reference extracted from the application), a **database or connection proxy** (pooling, failover, query routing, read/write splitting), and **client SDK stubs**. The design considerations are the class-level ones amplified: it becomes a chokepoint (so a single point of failure and a shared blast radius), it adds a latency hop and an operational surface to deploy, version, and observe, and policy in the proxy is invisible from the application's source — good for uniformity, bad for local reasoning. It's the wrong call when the wrapper carries no policy (pure delegation "for flexibility"), when the concern needs domain knowledge the proxy can't see (row-level authorization, business validation), when a stack of proxies makes the effective behavior unpredictable, or when the latency/ops cost exceeds the benefit for the traffic involved.
go deeper
Name a couple of real examples — an API gateway or reverse proxy sitting in front of services — and note it's the same 'stand in front and control access' idea.
Add what these layers actually do (TLS, caching, rate limiting, routing) and the basic cost: an extra hop and another thing to operate.
Discuss sidecars as extracted smart-reference proxies, retry amplification, ordering of edge concerns, and the limits of transport-level policy versus domain authorization.
Own the decision framework: which concerns belong at which layer, blast radius and failure domains, why gateway-only authn violates zero trust, and how to prevent the proxy tier from accreting business logic.
## The pattern, scaled GoF Proxy is described at object scope, but the intent — *a surrogate that controls access* — is scale-free. The same structure recurs at every layer: | Scope | Instance of Proxy | What it controls | |---|---|---| | Object | Lazy entity, security wrapper, memoizing wrapper | Creation, permission, recomputation | | Process | Client stub / SDK, connection pool wrapper | Where the subject is; resource reuse | | Host | Sidecar (service mesh data plane) | mTLS, retries, timeouts, circuit breaking, telemetry, traffic shifting | | Cluster edge | API gateway, reverse proxy, CDN, WAF | Authn, rate limiting, routing, caching, TLS, request/response shaping | | Data tier | DB proxy / connection multiplexer | Pooling, failover, read-replica routing, query limits | The **sidecar** is the clearest architectural example: cross-cutting remote-call concerns that used to be a smart-reference proxy *inside* every application (retry, timeout, circuit breaker, tracing) are extracted into a co-located process, so they're implemented once and are language-agnostic. That is literally "pull the proxy out of the object and give it its own address space". ## What you gain - **Uniform policy** — one place enforces TLS, authn, rate limits, or retry semantics across many heterogeneous services. - **Independent evolution** — policy changes ship without redeploying application code. - **Legacy insulation** — a proxy can front an unmodifiable system to add auth, caching, or a modern protocol (a common strangler-fig on-ramp). - **Observability chokepoint** — a natural place to emit consistent metrics and traces. ## What you pay 1. **A hop of latency** per proxy, per direction, plus serialization if the protocol changes. 2. **A failure domain.** The proxy is on the critical path; its outage is everyone's outage. Needs redundancy, health checks, and graceful degradation. 3. **Operational surface.** Another artifact to build, configure, version, roll out, and debug — with its own config language whose semantics may not match your intent. 4. **Invisible behavior.** Retries or caching configured in the mesh don't appear in application source. Combined with app-level retries you get *retry amplification* (3 × 3 = 9 attempts) and load storms during partial failure. Decide, once, which layer owns which concern. 5. **Debuggability.** "Where did this 403 come from?" now has five candidate answers. Trace propagation and per-hop request IDs stop being nice-to-have. 6. **Semantic mismatch.** Proxies see transport-level facts (path, headers, method). Domain policy — "a manager may approve invoices under 10k in their own region" — cannot be expressed there without duplicating domain knowledge at the edge, which then drifts. ## When adding a proxy is the wrong call - **No policy.** A wrapper that forwards every call unchanged "so we can add things later" is speculative generality: cost now, benefit maybe. Add it when the second implementation or the first policy actually exists. - **Domain-dependent authorization or validation.** Push it to where the data and the invariants are; the edge should do coarse authn/authz, not business rules. - **Latency-critical, high-fanout internal calls** where the added hop is a large fraction of the budget and the policy could be a library. - **Stacked proxies with overlapping concerns** (gateway retries + mesh retries + client retries; two caches with different TTLs). The effective behavior is emergent and untestable; consolidate ownership per concern. - **When it hides a modeling problem.** A proxy that patches responses to fix a bad upstream contract is a bandage; two teams need to change the contract. - **When it becomes a business-logic host.** Gateways accreting transformations turn into an ESB — the anti-pattern of "smart pipes, dumb endpoints". Keep the proxy's logic generic and policy-shaped. ## How to decide Ask: (1) What policy does this enforce that the endpoints cannot enforce as well? (2) Is the policy uniform across everything behind it, or does it need per-target knowledge? (3) What is the added p99 latency and the blast radius when it fails? (4) Which layer *owns* retries, caching, and authn — and is that written down? If the policy is uniform, generic, and infra-shaped, a proxy is the right seam. If it's per-service and domain-shaped, it belongs in the service. ## Continuity with the class-level pattern Every hazard from the object-level pattern reappears, magnified: transparency that misleads (a mesh retry looks like a slow call), bypass (a service reachable directly, not through the gateway — hence zero-trust designs that authenticate at the endpoint too), identity confusion (the client IP the service sees is the proxy's unless forwarded headers are handled correctly), and ordering (WAF before authn before rate limiting before cache — get it wrong and you cache authenticated responses or rate-limit after doing the expensive work).
- Your service retries failed calls, the sidecar retries, and the gateway retries. What happens during a partial outage?Retry amplification: 3 × 3 × 3 = up to 27 attempts per user request, turning a degraded dependency into a self-inflicted load storm that prevents recovery. Assign retry ownership to exactly one layer, budget total attempts end to end, and use backoff with jitter plus circuit breaking so retries stop when the dependency is clearly down.
- If the gateway already authenticates every request, why authenticate again in the service?Because the gateway is a chokepoint by convention, not by physics — anything that reaches the service directly (internal callers, misconfigured network policy, a compromised pod) bypasses it. This is the class-level 'protection proxy can be bypassed' problem at network scale, and the reason zero-trust architectures verify identity at the endpoint as well.
- When is a pass-through wrapper with no logic justified?Rarely, and only with a concrete near-term reason: an anti-corruption boundary you're about to swap implementations behind, a seam needed to test an untestable dependency, or a published interface that must stay stable while the implementation moves. Absent that, it's indirection whose cost is paid on every read of the code.
A country's customs and border control. It enforces one uniform policy for everyone entering (great), it's a single point everyone must pass (a bottleneck and an outage risk), and it can only check what's visible at the border — it cannot enforce the internal rules of the company you're visiting.
saying these in an interview costs you the question
- Adding a proxy layer 'for future flexibility' with no policy to enforce today
- Treating an API gateway as the only authorization boundary in an otherwise trusted internal network
- Letting retries and caching be configured at three layers with no single owner
- Migrating domain rules into the gateway until it becomes a business-logic hub
- Ignoring the added p99 latency and the blast radius of a shared chokepoint