Design problems
Classic building blocksMediumSDE-2 25 min

Design an API gateway

Asked at
AmazonGooglePhonePeRazorpayStripeSwiggy

The interviewer asks

“Design an API gateway that sits in front of hundreds of microservices. It should handle routing, authentication, rate limiting and observability for both public and internal APIs.”

What a strong answer looks like

A strong answer separates the data plane (a fleet of stateless proxies on the hot path) from the control plane (where routes, plugins and keys are configured and then pushed to the proxies). On the request path the gateway terminates TLS, matches a route, runs a short plugin chain (auth, rate limit, transforms), and forwards to a healthy upstream instance with timeouts, retries and circuit breakers. The deep dives: keeping added latency under ~5 ms p99 by never calling a database per request (local JWT validation, cached API keys, local-plus-global rate limits), safe config propagation, and resilience to slow upstreams. Mention multi-tenancy, observability (trace IDs, per-route metrics) and why the gateway must not become a dumping ground for business logic.

Concepts to mention

Data plane vs control planeTLS terminationRoute matching (radix tree)Local JWT validation with cached JWKSDistributed rate limiting (token bucket, Redis)Timeouts, retries with budget, circuit breakersBulkheads per upstreamConfig push with canary rolloutRequest ID and distributed tracingStateless horizontal scalingFail open vs fail closed

1 Requirements

Functional

  • Route requests by host, path, method and headers to the right backend service and version
  • Authenticate callers: JWT/OAuth2 for users, API keys or mTLS for partners and services
  • Rate limit and enforce quotas per API key, user, IP and route
  • Apply timeouts, retries and circuit breaking per route
  • Transform requests and responses (header injection, request IDs, simple REST to gRPC mapping)
  • Emit access logs, metrics and traces for every request
  • Let teams change routes and policies through a self-serve control plane without restarts

Non-functional

  • Added latency: < 2 ms p50, < 5 ms p99 over the backend time
  • Throughput: 500k requests/s peak (festive sale), linearly scalable by adding nodes
  • Availability: 99.99%; the gateway must be more available than any service behind it
  • Config changes visible on all nodes within ~10 s
  • Rate-limit accuracy within ~5% of the configured limit across the fleet
  • No single point of failure across 3 availability zones

Out of scope (say it out loud)

  • Full service mesh for east-west traffic (discuss briefly)
  • Developer portal and API monetisation UI
  • Business logic and data aggregation (belongs in BFF services)

2 Estimates

Peak QPS~500k req/sNormal ~100k req/s; sale-day peaks 5x (e.g. a festive sale or IPL final)
Gateway nodes~50-60 nodes (plus 30% headroom)An Envoy/NGINX node on 8-16 vCPU handles ~10-20k req/s with TLS and plugins; 500k ÷ 10k = 50
Bandwidth~4 Gbps in, ~16 Gbps out500k × 1 KB request ≈ 0.5 GB/s; 500k × 4 KB response ≈ 2 GB/s
Rate-limit counter ops~50k Redis ops/sIf each node syncs local counters to Redis every 100 ms per active key, instead of per request: ~50 nodes × ~100 hot keys × 10/s
Route table~5k routes, < 10 MB300 services × ~15 routes; fits in memory on every node
Access logs~40 TB/day500k req/s × ~1 KB log × 86,400 ≈ 43 TB at peak rate; sample or aggregate for long retention

3 API

EndpointWhat it does
ANYhttps://api.example.com/{service}/{path}Client traffic proxied to the matched upstream
POST/admin/v1/routesCreate a route: match rules, upstream, timeouts, retries, plugins
PUT/admin/v1/routes/{id}Update a route; creates a new config version
POST/admin/v1/consumers/{id}/keysIssue an API key with plan and quota
gRPCxDS / ConfigStream(node_id, last_version)Gateway nodes stream config snapshots from the control plane
GET/healthzLoad balancer health check for each gateway node

4 Data model

routesPostgres (control plane)
route_id (PK)hostspath_patternmethodsupstream_idtimeout_msretry_policyplugins (JSONB)versionowner_team

Source of truth with audit history. Compiled into an immutable snapshot (trie / radix tree for paths) and pushed to nodes.

upstreamsService registry (Consul / Kubernetes endpoints)
upstream_idservice_nameendpoints[]healthweights

Endpoints change often; gateway watches the registry instead of polling.

api_keysPostgres + local in-memory cache
key_hash (PK)consumer_idplanscopesstatuscreated_at

Store only a hash of the key. Nodes cache key → consumer for ~60 s; revocations are pushed as events.

rate_limit_countersRedis Cluster
rl:{consumer}:{route}:{window} → countTTL = window

Sharded by consumer. Used for global limits; nodes also keep local token buckets.

access_logsKafka → ClickHouse / S3
tsrequest_idroute_idconsumer_idstatuslatency_msupstream_latency_msbytes

Columnar store for per-route dashboards and per-consumer usage billing.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAHTTPSTCProute /ordersroute /paymentsglobal limitsfetch JWKS (cached)store configpush config (xDS)watch endpointsaccess logsingestMobile / Web /PartnersGateway Nodes (Envoy)L4 LB / AnycastAuth / IdP (JWKS)Orders ServicePayments ServiceControl PlaneKafka (logs, events)Redis (rate limits)ClickHouse(analytics)Config DB (Postgres)Service Registry
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Proxy an authenticated request

  1. 1Client calls POST /orders over HTTPS; the L4 load balancer picks a gateway node in the nearest zone
  2. 2Gateway terminates TLS, assigns a request ID and trace ID (or keeps the incoming ones)
  3. 3Route matching on host + path in an in-memory radix tree selects route 'orders-create' (microseconds)
  4. 4Auth plugin validates the JWT signature locally using cached JWKS keys and checks expiry, audience and scopes; no network call
  5. 5Rate-limit plugin checks the local token bucket for (user, route); global counters are synced to Redis in the background
  6. 6Gateway picks a healthy Orders instance (least-request load balancing, outlier ejection) and forwards with a 2 s timeout and the user ID in a trusted header
  7. 7Response returns; gateway records latency metrics, writes an access log line to a local buffer that ships to Kafka

Roll out a route change

  1. 1Team updates the route via the admin API; control plane validates it (schema, conflicts, ownership) and stores version N+1
  2. 2Control plane compiles a new snapshot and pushes it to 5% of gateway nodes (canary)
  3. 3Automatic checks compare error rate and latency of canary nodes vs the rest for a few minutes
  4. 4If healthy, the snapshot rolls to all nodes; each node swaps config atomically without dropping connections
  5. 5If unhealthy, the control plane pushes version N back; nodes also keep last-known-good config locally

7 Deep dives

Keeping the hot path fast: no database per request

The gateway adds latency to every API call, so the rule is: nothing on the request path waits on a remote store unless it must. Routes are compiled into an in-memory radix tree, so matching is microseconds. JWTs are validated locally: the gateway caches the identity provider's public keys (JWKS), refreshes them every few minutes and on an unknown key ID, and checks signature, expiry and scopes. Opaque API keys are hashed and looked up in a local cache backed by Postgres, with a TTL of ~60 s and push-based revocation events for fast blocking. Plugins run in a fixed order and must be cheap; anything slow (fraud scoring, content scanning) belongs in a service, not in the gateway. Use an event-loop proxy (Envoy, NGINX/OpenResty) with keep-alive connection pools to upstreams so TCP and TLS handshakes are not paid per request. With this, the gateway's own cost stays ~1-2 ms at p50. Measure it separately: log total latency and upstream latency, and alert on the difference.

Distributed rate limiting across 50 nodes

A limit like '1,000 requests/min per API key' is global, but traffic for one key spreads across 50 nodes. Options: (1) Call Redis on every request with an atomic Lua script (sliding-window or token bucket). Accurate, but it adds ~0.5-1 ms and a Redis dependency to every call; at 500k req/s that is a big Redis cluster. (2) Pure local limits: divide the limit by node count. No network cost, but uneven load balancing makes it unfair. (3) Hybrid, which most large gateways use: each node keeps a local token bucket and syncs its consumption to Redis every ~100 ms or every N requests, getting back the global count and adjusting its local share. Accuracy is within a few percent, and Redis load drops by 10-100x. Use sliding-window counters for fairness at window edges. If Redis is unreachable, fail open with local-only limits (protect availability) for normal APIs, but fail closed for abuse-sensitive routes like OTP or login. Return 429 with Retry-After and X-RateLimit-Remaining headers.

Resilience toward upstreams

The gateway sees every service failure first, and it must not amplify it. Each route has a timeout lower than the client's (for example 2 s for the gateway vs 5 s on the app), so a slow service frees gateway resources. Retries only for idempotent methods (GET, or POST with an Idempotency-Key), at most 1-2 attempts, with jittered backoff and a retry budget (retries ≤ 10-20% of requests) so retries cannot triple the load during an outage. Outlier detection ejects instances that return consecutive 5xx. Circuit breakers cap concurrent requests and pending queue per upstream: when Payments is slow, its connection pool fills and new Payments requests fail fast with 503, while Orders traffic is unaffected. This is bulkheading per upstream. For a festive sale, priority-based load shedding drops low-value traffic (recommendations) before checkout. Hedged requests can cut tail latency for idempotent reads but double load, so use them only for a few critical paths.

Control plane vs data plane

Split the system so the data plane keeps working when the control plane is down. The control plane owns the config database, validation, RBAC (which team may change which routes), audit logs, and compilation of snapshots. Data-plane nodes subscribe over a streaming API (Envoy's xDS is the standard) and get versioned, complete snapshots, not individual diffs, so a node that reconnects simply takes the latest version. Nodes keep the last applied config on disk; if the control plane is unreachable they keep serving with it. Changes roll out progressively with health gates. Validation catches route conflicts (two teams claiming /v1/users) and dangerous settings (timeout 0, retries 10). This split is also how you offer the gateway as a platform: hundreds of teams self-serve routes without a central team editing NGINX files and doing reloads.

8 Trade-offs

One central gateway vs per-domain gateways and BFFs

One gateway gives one place for auth and limits but becomes a shared bottleneck in process and blast radius. Larger companies run several gateway clusters (public, partner, internal) built on one platform, plus BFF services per client type for aggregation. Keep business logic out of the gateway either way.

Build on Envoy/NGINX vs managed gateway

Managed (AWS API Gateway, Apigee) is fast to start and includes keys and quotas, but costs per request (expensive at 500k req/s), adds latency and limits customisation. Self-hosted Envoy or Kong gives control and low cost at scale but needs a platform team. Choose managed for small scale, self-hosted at large scale.

Fail open vs fail closed

If the rate limiter or auth key cache is unavailable, failing open keeps the business running but removes protection; failing closed protects backends but turns a small dependency failure into a full outage. Decide per route: auth fails closed (with cached keys as a buffer), most rate limits fail open.

9 Bottlenecks

TLS handshakes during a traffic spike

A sale start brings millions of new connections; full TLS handshakes are CPU heavy. Use TLS 1.3, session resumption, ECDSA certificates, keep-alive, and pre-scale gateway nodes before known events instead of relying on reactive autoscaling.

Redis hot key for a big partner

One very large API consumer makes its rate-limit key hot on one Redis shard. Use the hybrid local-bucket approach so the key is touched only on sync, or split the key into N sub-keys and sum them.

10 Failure handling

A gateway node crashes

The L4 load balancer health check removes it within seconds. Nodes are stateless (state is in config snapshots and Redis), so others take the load. Keep N+2 capacity per zone.

Control plane outage

Gateways keep serving with their last-known-good snapshot. Only config changes are blocked. Emergency changes (block an abusive key) can go through a small, separately deployed denylist channel.

Upstream service melts down

Timeouts, circuit breakers and per-upstream connection limits isolate it. The gateway returns fast 503s for that route and protects its own threads and memory so other routes are unaffected.

11 What impresses

  • Stating a latency budget for the gateway itself and how to measure it
  • Hybrid local + global rate limiting with clear accuracy vs cost reasoning
  • Retry budgets and idempotency rules so retries do not amplify outages
  • Explaining xDS-style versioned snapshots and last-known-good config
  • Per-route decisions on fail open vs fail closed
  • Keeping business logic out of the gateway and using BFFs for aggregation

12 Mistakes

  • Calling the auth service or a database on every request
  • A single gateway instance or a gateway with in-memory sessions (not stateless)
  • Retrying all failed requests, including non-idempotent POSTs, with no limit
  • Putting aggregation and business rules in the gateway until it is a monolith
  • Hand-editing config files and reloading nodes without validation or canaries
  • Forgetting observability: no request IDs or per-route metrics

13 Follow-ups

Q1How do you handle WebSocket and long-lived streaming connections?
Q2How would you version APIs and run two versions of a service side by side?
Q3How do you revoke a leaked API key within seconds?
Q4How is this different from a service mesh, and do you need both?
Q5How would you add an LLM gateway for AI endpoints (token-based limits, model fallback)?
Q6How do you protect checkout during a festive sale when traffic is 10x?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free