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.
Design a quota and limits management framework for a cloud platform providing managed services to tenants. Requirements: dynamic per-tenant quotas, per-service limits, billing integration for overages, admin overrides, and enforcement in both control plane and data plane while maintaining low latency for enforcement checks.
Sample Answer
Requirements & constraints:
- Dynamic per-tenant quotas (can change at runtime), per-service limits, billing for overages, admin overrides, enforcement in control-plane (API calls, provisioning) and data-plane (runtime requests) with low-latency checks (<ms for hot-path).
High-level architecture:
- Quota Service (central control-plane): authoritative store, policy engine, change management, audit, admin UI/API, billing integration.
- Fast Cache/Config Store: distributed, strongly-consistent cache (e.g., Redis Cluster with CRDT or etcd for control), updated via pub/sub.
- Enforcement Agents:
- Control-plane enforcers call Quota Service for non-latency-critical ops.
- Data-plane enforcers use local caches and token-bucket/semaphore algorithms; report usage async.
- Usage Collector & Aggregator: stream (Kafka) for metering events, normalization, aggregation, forwarding to Billing.
- Billing Service: applies pricing, thresholds, generates invoices/notifications.
- Admin/Policy UI + Audit log.
Data flow:
- Admin updates quota -> Quota Service validates, writes to authoritative DB and publishes update to pub/sub.
- Cache nodes subscribe and update local enforcement config within <100ms.
- Data-plane enforcer checks local token-bucket; on allow, consumes tokens; emits usage event to Kafka.
- Aggregator computes rollups; Billing charges overages or notifies based on thresholds.
Key design choices & reasoning:
- Use local caches for low-latency enforcement; authoritative store for correctness.
- Token-bucket for rate/throughput limits; counters with windowing for resource quotas.
- Use optimistic enforcement: allow short bursts even if slightly stale, reconcile with billing and triggers for hard limits when necessary.
- Admin overrides flagged and pushed immediately; include versioning for easy rollback.
- Ensure idempotent usage events and at-least-once delivery; aggregator deduplicates by event-id.
Scalability & reliability:
- Shard quotas by tenant; scale collectors and enforcers horizontally.
- Use circuit-breaker: if cache stale or disconnected, fall back to conservative defaults (deny or limit) per policy.
- Strong auditing for compliance; expose telemetry.
Trade-offs:
- Cache staleness vs latency: choose short TTL + push updates to reduce staleness.
- Consistency vs availability: prefer availability for data-plane with reconciliation and billing adjustments.
Operational notes:
- Provide APIs for sales to query tenant quotas and simulate overage costs.
- Test with chaos scenarios (partition, clock skew) and load testing to validate enforcement latency and billing correctness.
Design rate-limiting and backpressure at an API Gateway handling mixed endpoints at 10k RPS. Explain token-bucket vs leaky-bucket algorithms for rate limiting, per-user vs per-endpoint quotas, how caching can reduce enforced limits, and how the Gateway should signal backpressure to downstream services and clients.
Sample Answer
Requirements & constraints:
- Mixed endpoints at 10k RPS total, mixed SLAs (interactive vs batch), per-user and per-endpoint fairness, low latency for critical paths, protect downstream services from overload.
Rate-limit algorithms — comparison:
- Token Bucket: allows bursts up to bucket capacity while enforcing average rate. Good for interactive APIs that tolerate short bursts. Implementation: token refill at rate R, consume 1 per request; reject or queue when empty.
- Leaky Bucket: enforces a steady outflow (smooths bursts). Implemented as queue with fixed drain rate; excess requests are dropped/queued. Good for protecting downstream throughput and enforcing constant-rate SLAs.
Trade-off: token-bucket = flexible bursts; leaky-bucket = predictable steady load. Combine: token bucket for client-facing shaping, leaky bucket for downstream egress.
Quotas: per-user vs per-endpoint
- Per-user: prevents noisy neighbors; use user-id / API-key tokens, sliding windows + token-bucket for fairness. Enforce hard daily/monthly quotas at payment tier.
- Per-endpoint: protect expensive or stateful endpoints (e.g., write-heavy or DB-backed). Assign lower rate limits and stricter queuing.
- Policy layering: global gateway limit → per-endpoint limit → per-user limit. Apply highest-precedence policy (most restrictive).
Caching to reduce enforced limits
- Cache idempotent GETs at gateway or edge CDN with TTLs and cache keys including auth scope. Hits bypass downstream and don’t consume backend quota; count cached responses against client quota optionally or exclude for free-tier endpoints. Use stale-while-revalidate to improve perceived availability during throttling.
Backpressure signaling
- To downstream services: expose health and load endpoints; gateway uses circuit-breakers + dynamic throttling (reduce concurrency, queue depth). Send X-Downstream-Load or custom headers and use gRPC/HTTP2 flow-control for stream-aware backpressure.
- To clients: return 429 Too Many Requests with Retry-After header and a JSON body with quota details and next-available time. For gradual degradation, reply with 202/200 with degraded response or cached stale data. For long-running jobs, accept and return job id (202) instead of immediate failure.
Observability & operations: - Emit metrics (per-key, per-endpoint rates, rejections, queue lengths), alarms for sustained throttling, dashboards.
- Policies stored in config service; support real-time updates and distributed token sync (Redis/ETCD or local with periodic refill + best-effort consistency).
Security & fairness: - Use authenticated keys, IP-based fallback, and bot detection to prevent quota circumvention.
This design balances burst tolerance, predictable backend load, per-customer fairness, and clear client signals during overload.
Implement a thread-safe token bucket rate limiter in Python that supports per-user rate limits and burst capacity. Provide an allow_request(user_id) method returning True/False. Explain how your design is efficient in memory, how you handle concurrency, and how you would adapt it to run across multiple API gateway instances in production.
Sample Answer
To implement a thread-safe per-user token bucket in Python, store a lightweight bucket per user (capacity, refill_rate, tokens, last_ts). Use per-user locks or a sharded lock table to reduce contention. For production multi-instance use, move state to a central datastore (Redis) using atomic Lua scripts or use a consistent-hashed local cache with periodic sync.
import time
import threading
from collections import defaultdict
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity # max tokens (burst)
self.refill_rate = refill_rate # tokens per second
self.tokens = capacity
self.last_ts = time.monotonic()
self.lock = threading.Lock()
def allow(self):
with self.lock:
now = time.monotonic()
elapsed = now - self.last_ts
# refill
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
self.last_ts = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
class RateLimiter:
def __init__(self, default_capacity=10, default_rps=1):
self.buckets = {} # user_id -> TokenBucket
self.default_capacity = default_capacity
self.default_rps = default_rps
self.global_lock = threading.Lock() # protects creation of buckets
def allow_request(self, user_id, capacity=None, rps=None):
# lazy-create bucket with double-checked locking
if user_id not in self.buckets:
with self.global_lock:
if user_id not in self.buckets:
cap = capacity or self.default_capacity
rate = rps or self.default_rps
self.buckets[user_id] = TokenBucket(cap, rate)
return self.buckets[user_id].allow()
Key points and reasoning:
- Memory efficiency: Each user stores only a small object (4 fields + lock). For very large user bases, use an LRU eviction or TTL to remove idle users to bound memory.
- Concurrency: Per-bucket locks ensure minimal contention (requests for different users don't block). Creation uses a global lock but only briefly; can be sharded (N mutexes by hash) to reduce contention.
- Complexity: allow_request is O(1) time and O(U_active) space where U_active is active users currently tracked.
- Edge cases: clock monotonic used to avoid system clock jumps; handle fractional refill properly; handle high throughput by increasing shards or using lock-free atomic primitives if needed.
- Distributed operation: Use Redis with a Lua script that atomically refills and consumes tokens (stores tokens and last_ts in a Redis hash). That provides strong consistency across API gateway instances and avoids stale local state. For lower latency, run local token buckets as a leaky bucket window and sync counters to Redis periodically (token bucket as a local cache + Redis as source-of-truth), but accept potential slight over-allocation. Use client-side consistent hashing or sticky sessions to reduce coordination cost.
Trade-offs:
- Local in-process buckets minimize latency but don't enforce global limits across instances.
- Redis/Lua enforces global limits with extra network latency; mitigate by sharding or pipelining and choosing appropriate TTLs.
Design an API-level cost-aware throttling system that enforces budget caps: when a tenant approaches its budget, the system throttles or deprioritizes low-value operations while guaranteeing SLAs for premium customers. Describe the policy engine, priority queues, billing integration, notification flow, and reporting needed to operate this safely.
Sample Answer
Requirements & constraints:
- Per-tenant monthly budget caps (dollars)
- Preserve SLAs for premium tiers (latency, success rate)
- Deprioritize or throttle low-value ops (batch, analytics) as budget hits thresholds
- Real-time enforcement (<100ms decision), auditability, billing accuracy, alerting, safe defaults
High-level architecture:
API Gateway → Throttling Proxy (fast path) → Priority Queueing Layer → Execution
Supporting services: Policy Engine, Billing DB, Usage Aggregator, Notification Service, Reporting/Analytics.
Policy engine:
- Rule-based + weight-scored policies stored as versioned JSON (thresholds: 70/85/95% of budget).
- Inputs: tenant ID, tier, current spend, projected burn rate, operation metadata (op type, cost weight, SLA tag).
- Decision: action {allow, mark-deprioritize, delay(ms), reject, route-to-queue-priority}.
- Fast path: precomputed per-tenant token buckets and in-memory thresholds in the proxy; policy engine refreshes via push or pub/sub. Complex decisions via cached feature flags.
Priority queues:
- Multi-level queues (high/medium/low) per region; premium SLA ops map to high and bypass low-cost throttles.
- Queue admission enforced by the proxy using policy decision; workers pick from queues respecting concurrency caps and fairness (weighted fair queueing).
- Backpressure: when low queue fills, oldest low-value requests are rejected with explicit error codes and retry-after.
Billing integration:
- Real-time usage aggregator emits cost events (per-op estimated cost) to Billing DB and streaming pipeline (Kafka).
- Billing reconciles estimates with post-facto metering for accuracy; policy engine uses real-time estimated spend + smoothing model (EWMA) to avoid oscillation.
- Enforcement uses conservative rounding up of estimated cost to prevent overspend.
Notification flow:
- Multi-channel alerts as thresholds hit: 70% (informational email + dashboard), 85% (warnings + webhook to tenant), 95% (pre-throttle + immediate webhook/SMS for enterprise).
- Include actionable payloads (remaining budget, top contributing operations, suggested mitigations).
- Escalation and operator runbooks for false positives.
Reporting & audit:
- Time-series dashboards: spend, ops by priority, throttle events, SLA compliance, per-tenant projections.
- Audit logs for each decision: policy version, inputs, decision id — stored immutable (append-only) for compliance.
- Postmortem reports and daily aggregated billing reports.
Safety & operational considerations:
- Fail-open vs fail-closed configurable per tier; default: fail-open for free tier, fail-closed for premium.
- Circuit-breakers and cooldown periods to avoid rapid flip-flopping.
- Canary rollout for policy changes; real-time simulation mode (dry-run) to validate impact.
- Cost estimation accuracy trade-off vs latency: favor low-latency approximate cost with reconciliation.
Metrics to monitor:
- Throttle rate, SLA breach rate, billing variance (estimation vs final), false-positive throttle incidents, queue latencies.
Trade-offs:
- Strong consistency in spend is costly; use eventual consistency with conservative enforcement to stay within budgets.
- In-memory caches give low latency but need strong invalidation for policy updates.
This design provides real-time, budget-aware throttling while guaranteeing premium SLAs, with instrumentation for safe operation and continuous improvement.
Design a system to enforce global rate limits such as 1000 requests per minute per user across multiple regions with low per-request latency. Discuss algorithms (fixed window, sliding window, token bucket), data stores (local counters, Redis, CRDTs), trade-offs between strong and eventual consistency, and how to support burst behavior and reconciliation of counters after partitions.
Sample Answer
Requirements & constraints:
- Global per-user limit: 1000 req/min (single quota) across multiple regions
- Low per-request latency (ms)
- Support bursts, eventual reconciliation after partitions
- Operate at large scale, multi-region failover
High-level approach:
- Enforce limits locally for low latency, coordinate globally for correctness. Use a hybrid: token-bucket at edge with periodic global sync and a strong-global option for high-risk requests.
Architecture:
- Edge API Gateway (per-region): maintain local token-bucket per user in-memory (fast, O(1)).
- Local backing store: Redis (clustered, local region) to persist counters and share among gateways.
- Global coordinator: sharded Redis/consistent-hash or a central rate-limit service that keeps authoritative quota per user (optionally partitioned by user id). For cross-region consensus use CRDTs or conflict-resilient logs.
- Background reconciliation: periodic reconciliation job that aggregates regional usage to global authoritative store and repairs drift.
Algorithms & trade-offs:
- Fixed window: simple, but causes spikes at window boundaries (not ideal).
- Sliding window (using sorted set or rolling counters): smoother accuracy, higher storage & compute per request.
- Token bucket (recommended): allows natural bursts, simple local enforcement; tokens refill at global rate. Edge consumes tokens quickly; when depleted, fall back to global check.
Consistency approaches:
- Strong consistency: every request checks global authoritative store (Redis with strong consistency or a consensus layer). Guarantees correctness but adds cross-region latency and lower availability.
- Eventual consistency (recommended default): local token buckets give low latency and allow bursts; periodic sync to global store accepts temporary overuse. Use configurable safety margins (reserve tokens) to reduce overshoot.
Supporting bursts:
- Token buckets allow bursts up to bucket size. Tune bucket depth to business needs (e.g., allow 2000 tokens max for short bursts).
- Use short-term local borrowing: edge can borrow from a regional pool; borrowing is recorded and reconciled.
Reconciliation after partitions:
- Use idempotent, append-only usage logs per edge region (e.g., per-minute buckets). On reconnection, aggregate regional deltas and apply to global authoritative counter.
- Conflict resolution: prefer max(consumed, sum) depending on policy. For strict enforcement, cap excess requests and issue compensating actions (throttle next minute, notify).
- CRDTs: use G-Counters per region (monotonic increments) so merging is commutative; global usage = sum of regional counters. CRDTs avoid lost updates and make reconciliation trivial.
Operational notes & optimizations:
- Shard global store by user-id to scale. Use consistent hashing to place authoritative shard near heavy users.
- Cache negative results (user exceeded) at edge with TTL to avoid hot keys.
- Use probabilistic admission: when close to limit, require occasional global check to avoid sustained overshoot.
- Metrics & alerts: emit per-region and global metrics, monitor reconciliation drift.
Trade-offs summary:
- Low latency vs strong correctness: local token buckets favor latency and availability; global checks favor correctness at cost of latency.
- Sliding window offers accuracy but higher cost; token bucket gives natural burst control and simpler local logic.
- CRDTs and append-only logs simplify reconciliation; consensus-based global counters suit strict enforcement but reduce availability/latency.
This design lets you tune strictness per customer (e.g., premium users get global-strong checks) while keeping low-latency defaults and safe reconciliation after partitions.
Unlock Full Question Bank
Get access to all 9 Rate Limiting, Throttling and Quota Management interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.