Rate Limiting, Throttling and Quota Management Questions
Protecting API capacity and enforcing fair use: rate-limiting algorithms (token bucket, leaky bucket, fixed/sliding window), per-client quotas, throttling responses (429 semantics, Retry-After), and tiered plan enforcement. Covers where to enforce limits (gateway vs. service), distributed counters, and graceful degradation under load.
You plan to migrate a widely-used REST API schema to a new contract. Compare the migration approaches for REST (additive fields, content negotiation, new endpoints) versus GraphQL (field deprecation, schema evolution). Describe contract testing, consumer-driven contracts, and automated compatibility checks to avoid breaking clients when changing payloads.
Sample Answer
Requirements & constraints:
- Maintain backward compatibility for many existing clients (low tolerance for breaks).
- Allow new features and stricter typing over time.
- Fast rollout with automated verification and clear rollback paths.
High-level comparison — REST:
- Additive fields: safest. Add new optional fields (or nullables) so older clients ignore them. Use semantic versioning only for breaking changes.
- Content negotiation: introduce a new media type (e.g., application/vnd.myapi.v2+json) or accept header to serve different contracts from same endpoint. Pros: single URL; Cons: added server branching, complexity in caches and intermediaries.
- New endpoints: /v2/resource. Pros: clear separation and simple server logic; Cons: duplication, client migration overhead, longer maintenance.
High-level comparison — GraphQL:
- Field deprecation: mark fields @deprecated, keep resolvers until clients stop requesting them. GraphQL introspection helps clients discover deprecations.
- Schema evolution: add new fields and types freely (non-breaking). Breaking changes require field removal or type changes — manage via deprecation windows and coordinated releases.
- Pros vs REST: GraphQL’s strong typing + introspection makes discovery and gradual evolution easier; but a single schema means one mis-step can affect many clients.
Contract testing & techniques:
- Contract testing: verify producer and consumer agree on request/response shapes. Implement provider tests that assert the API still satisfies recorded consumer contracts.
- Consumer-Driven Contracts (CDC): consumers publish expectations (Pact, Spring Cloud Contract). CI for provider runs consumer contracts as tests; failures indicate breaking changes before release.
- Example flow: consumer generates contract in its CI -> contract stored in broker -> provider CI retrieves and runs provider verification tests.
Automated compatibility checks:
- Schema diffing: automated tools compare old vs new schemas (JSON Schema, OpenAPI diff, graphql-schema-diff) and classify changes (additive vs breaking).
- CI gates: block merges that introduce breaking changes; allow opt-in experimental flags for non-critical clients.
- Runtime canary & feature flags: roll new contract to a subset of traffic; monitor error rates, contract-violation logs, and usage of deprecated fields (GraphQL query usage metrics).
- Monitoring & observability: instrument request/response validation, record client schema versions, and alert on validation failures.
Operational practices & trade-offs:
- Maintain clear deprecation policy and timeline (e.g., deprecate→announce→monitor 90 days→remove).
- Use semantic versioning for REST major bumps; prefer additive-first approach to minimize client churn.
- For GraphQL, rely on introspection, usage analytics, and CDC to manage removals safely.
- Invest in automation (diffs + CDC + CI + canary + monitoring) — initial cost pays off by preventing breaks and speeding client migrations.
This combined approach (additive changes where possible, CDC-backed provider verification, automated schema diffing, canaries, and clear deprecation policies) minimizes client impact while enabling evolution.
Compare REST and GraphQL for public API design. Describe differences in API surface, typical use cases, how each affects caching, versioning, and rate limiting. For a mobile-first public API with many low-bandwidth clients, explain which you'd choose and why. Discuss trade-offs including overfetching, underfetching, toolchain, client complexity, and developer experience.
Sample Answer
High-level summary:
- REST: resource-oriented, many endpoints (URLs per resource/verb), uses HTTP verbs/status, simple caching and CDN friendliness.
- GraphQL: single endpoint with schema-driven graph queries; clients request exactly the fields they need.
API surface:
- REST exposes multiple endpoints (bigger URL surface). GraphQL exposes one endpoint but a rich, discoverable schema (introspection).
Typical use cases: - REST: simple CRUD, public APIs where HTTP caching and CDN are important, microservices-to-microservice calls.
- GraphQL: complex UIs (mobile/web) that need aggregated data from many resources, reducing round-trips and overfetching.
Caching:
- REST: straightforward: HTTP cache headers, CDNs, cache by URL/verb. Predictable.
- GraphQL: harder because same endpoint; caching requires persisted queries, normalized response caches (Relay/Apollo), or caching at resolver/DB layer. Use query hashing and CDN with persisted queries to improve caching.
Versioning:
- REST: often versioned via URL or headers (/v1). Clear but can proliferate.
- GraphQL: prefers schema evolution (deprecate fields, add fields) enabling non-breaking changes — fewer explicit versions if done carefully.
Rate limiting:
- REST: rate limit per endpoint or API key by requests; simple quotas.
- GraphQL: need rate-limiting by query complexity, depth, or cost, since one query can be expensive. Also basic per-request quotas still apply.
Mobile-first, many low-bandwidth clients — my choice:
- Choose GraphQL with caveats. Reasoning: mobile clients benefit from fetching exactly the fields they need and combining multiple resource fetches into one round-trip, reducing bandwidth and latency. To mitigate GraphQL downsides:
- Use persisted queries (small request payloads, cacheable).
- Implement query cost/depth limits and server-side complexity scoring.
- Use DataLoader/batching to avoid N+1 and efficient resolver caching.
- Provide server-side field-level caching and CDN for persisted queries.
- Offer an optional minimal REST fallback for very simple bulk downloads or analytics endpoints.
Trade-offs:
- Overfetching: GraphQL avoids it; REST often overfetches or needs many endpoints.
- Underfetching: REST can underfetch requiring multiple calls; GraphQL avoids this.
- Toolchain & DX: GraphQL offers strong tooling — typed schemas, codegen, playgrounds — improving DX but increasing initial complexity. REST has mature, simple tooling and is easier for public consumption.
- Client complexity: GraphQL clients (Apollo/Relay) add runtime complexity and bundle size — a concern on low-bandwidth/mobile; mitigate with lightweight fetch + codegen or use persisted queries. REST clients are simpler to implement.
- Developer experience: GraphQL speeds frontend iteration and reduces backend endpoint churn; REST is familiar, simpler to secure and cache.
Conclusion:
For mobile-first, low-bandwidth public API I'd pick GraphQL to minimize bandwidth and round-trips, while investing in persisted queries, query cost controls, and resolver-level caching. If public consumption simplicity, CDN-heavy caching, or strict per-URL rate limiting are primary constraints, choose REST.
Design an API platform that enforces rate limits per API key, per user, and per IP at large scale (100k RPS sustained). Explain architecture components, low-latency enforcement strategies (local cache + Redis), storage choices, sharding/hot keys handling, cross-region limits, telemetry, and how to expose limit info to clients.
Sample Answer
Requirements:
- Functional: enforce configurable rate limits per API key, per user, per source IP; show remaining quota to clients.
- Non-functional: sustain 100k RPS, <2ms enforcement added latency, global deployment, eventual strong/soft consistency for UX, robust telemetry.
High-level architecture:
- Edge/API Gateway (regionally deployed) receives requests, performs auth and quick local checks.
- Rate Limiting Service per region: two-tier enforcement — very-low-latency local cache + central Redis cluster(s).
- Control Plane: manage limit configs, policies, feature flags, rollout.
- Global Coordinator: for cross-region/global limits (see approaches).
- Telemetry pipeline: streaming logs -> Kafka -> real-time metrics (Prometheus/Grafana, alerting) + OLAP store for long-term analysis.
- Client-facing: expose headers (X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After) and a management API to query quotas.
Low-latency enforcement strategy:
- Local token-bucket cache in gateway (in-process or sidecar) holding short-lived counters (e.g., 500–2000ms TTL) to approve most requests without network hop.
- Redis as authoritative backing store per region, using Lua scripts for atomic token-bucket or fixed-window + sliding-window approximation. Gateway periodically syncs local tokens with Redis via async replenish requests or window reconciliation.
Storage and data model:
- Redis primary for counters (in-memory, fast TTL, supports atomic ops).
- Use Redis Cluster with hash slots; keys structured: rate:{scope}:{id}:{window} (e.g., rate:api_key:abc123:20251201T1500).
- Persist aggregated usage to a write-optimized datastore (Cassandra or BigQuery) for billing/analytics.
Sharding and hot keys:
- Hash-prefix keys to spread load. For very hot keys (popular API keys/users/IPs): detect via metric stream and apply strategies:
- Local hot-shard cache pinned to specific nodes.
- Use token leases: allow gateway to serve bursts locally for N tokens then reconcile.
- Backpressure: route hot-key traffic to dedicated Redis shards or an in-memory per-key token service.
- Rate-limit adaptive smoothing: increase window granularity to reduce Redis ops.
Cross-region/global limits:
- Two models:
- Stronger-consistency model: use a global Redis/consensus (e.g., Redis ACL with strong replication or central quorum) — higher latency; only for low-throughput global limits.
- Eventually-consistent model (recommended): maintain per-region quotas, aggregate usage asynchronously; enforce local conservative reservation (divide global quota across regions) and allow burst borrowing via periodic reconciliation. For strict cross-region prevention, use a lightweight global counter via a geographically distributed counter (e.g., CRDT counters or a centralized rate authority with request-level checks for rare operations).
Telemetry & observability:
- Emit events at gateway and rate service: decisions (allow/deny), latency, token remaining, Redis errors.
- Stream into Kafka -> metrics (Prometheus) + trace (Jaeger) + cluster alerting on error rates, increasing deny rates, hot-key saturation.
- Provide dashboards: per-key, per-user, per-IP QPS, 95/99 latencies, Redis latency/throughput, headroom.
Exposing limits to clients:
- Standard headers on each response:
- X-RateLimit-Limit: limit
- X-RateLimit-Remaining: remaining tokens
- X-RateLimit-Reset: epoch when bucket resets
- Retry-After on 429
- Management API to query current usage and forecast, web console for quota purchase/upgrade.
Resilience and safety:
- Fallbacks: if Redis unavailable, gateways use conservative local allowance and return 503/429 based on policy.
- Graceful degradation: soft-limits vs hard-limits, exponential backoff suggestions in responses.
- Testing: load tests with simulated hot keys, chaos testing for Redis and network partitions.
Trade-offs:
- Local caching reduces latency and Redis ops but risks temporary overshoot; choose conservative local token allotments and short TTLs.
- Strong global consistency increases latency; prefer hybrid per-region quotas + reconciliation for most APIs.
This design balances sub-ms enforcement for most requests, scalability to 100k RPS via sharded Redis and edge caching, and practical cross-region behavior for global limits with strong telemetry and client visibility.
Describe a test-at-scale strategy for validating your rate limiting and billing pipeline. Cover how to generate realistic traffic (replaying production traces), simulate bad actors, validate accuracy and idempotency of billed events, and run chaos tests on the metering pipeline. What acceptance criteria would you set before rolling to production?
Sample Answer
Approach/framework
- Break testing into: realistic load (replay), adversarial/surge scenarios, correctness (accuracy & idempotency), and resilience/chaos of the metering pipeline. Automate and run in a pre-prod environment that mirrors production topology (Kafka/streaming, DBs, billing services).
- Generate realistic traffic
- Replay production traces anonymized and time-warped to scale (Kafka topic of raw events). Use tools to transform timestamps and scale factor (e.g., k6/Locust or custom replayer) so arrival patterns, bursts, user-agents, and geo-distribution match production.
- Blend in synthetic clients for rare-but-important flows (large file uploads, long sessions).
- Simulate bad actors
- Create scenarios: credential stuffing (many distinct tokens), rapid repeated API calls, malformed/partial events, event reordering and duplicates, slow/half-open TCP connections.
- Use attack scripts and load tools plus IP spoofing/rate-limited clients; inject into replay stream.
- Validate accuracy & idempotency
- Instrument pipeline to attach deterministic event IDs and sequence numbers at ingress.
- Run reconciliation: generate a golden dataset by running the same input through a reference (slow, single-threaded) billing engine to compute expected billed events and totals.
- Compare outputs: per-customer usage, per-feature counts, monetary totals. Check for duplicates, missing events, and rounding errors. Use checksum hashes per event chain.
- Validate idempotency by replaying identical batches multiple times and asserting billing unchanged.
- Chaos tests on metering
- Inject faults: partition brokers, drop/duplicate messages, add latency, kill workers, simulate DB failover, overload downstream billing service.
- Use chaos tools (Chaos Mesh/Gremlin, tc/netem) orchestrated in CI.
- Ensure the pipeline supports backpressure, retries, exactly-once or at-least-once semantics as designed.
Acceptance criteria before release
- Functional: 99.9% of replayed events are processed end-to-end under nominal scale.
- Accuracy: billing variance ≤0.1% compared to golden run; zero double-billing in idempotency tests.
- Latency/SLA: 95th percentile metering-to-bill propagation < defined SLA (e.g., 5 min) and system stays within resource limits.
- Resilience: survive N simultaneous worker failures and broker partition for T minutes with automatic recovery and no data loss >0.01%.
- Observability: alerts detect anomalies within X minutes; reconciliation job completes within SLA.
- Runbook & rollback validated.
Why this works
- Replay ensures fidelity; golden-run + checksum ensures correctness; adversarial + chaos prove robustness. Automate these tests and make them gate checks in CI/CD before production rollout.
Describe the token bucket rate limiting algorithm: explain the key parameters (capacity and refill rate), how it enforces throughput while allowing bursts, and how it differs behaviorally from leaky bucket and fixed-window approaches. Provide a short example of when token bucket is preferable.
Sample Answer
The token bucket algorithm controls rate by maintaining a bucket of tokens that are added at a steady refill rate (tokens/second) up to a maximum capacity (the bucket size). To perform an action (e.g., send a request or packet) you must remove N tokens; if enough tokens exist the action proceeds immediately, otherwise it is delayed or rejected until tokens accumulate.
Key parameters:
- Refill rate (r): steady long-term throughput (tokens per second).
- Capacity (b): maximum tokens stored, defines allowable burst size.
How it enforces throughput while allowing bursts:
- Over long intervals the refill rate limits average throughput to r.
- Because the bucket can accumulate up to b tokens during idle time, a client can spend those tokens quickly and emit a burst up to b (plus the ongoing refill) without violating the long-term rate.
Behavioral differences:
- Leaky bucket (queue-based): enforces a fixed output rate by draining a queue at constant rate; excess arrivals are queued or dropped—it smooths traffic and does not allow bursts beyond queue size. Token bucket permits bursts up to capacity while still controlling average rate.
- Fixed-window counters: count events in discrete time windows (e.g., per second). They can allow large spikes at window boundaries (burstiness) and suffer from coarse granularity; token bucket provides smoother, fine-grained control and predictable burst allowance.
When token bucket is preferable:
- Throttling an API where you want to guarantee a steady average rate but allow clients to make short bursts after idle periods (e.g., user-triggered bulk uploads or interactive tools that occasionally need rapid requests). It balances user experience (bursts) with system protection (long-term rate).
Unlock Full Question Bank
Get access to all Rate Limiting, Throttling and Quota Management interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.