The rate limit isn't the problem. Hitting it without a plan is. The four-pattern stack that keeps you under the ceiling — and what to do when you're not.
Rate limits are infrastructure. Every API you consume has one, and every API you build should have one. The engineers who treat them as an edge case discover that assumption in production. The engineers who understand them in advance never do.
REST, GraphQL, gRPC, tRPC — the "which one" conversation happens before a line is written, and it matters because the answer shapes everything downstream: caching behaviour, client flexibility, typing guarantees, and who can consume the result. The honest framing in 2026 is that REST vs GraphQL is no longer a binary — most mature organisations use both, picking per use case. The genuinely new element is MCP-based consumption for AI agents, which sits alongside all three as a fourth option, not a replacement for any of them.
The practical rule
REST is the safe default for anything public or partner-facing. Reach for GraphQL specifically when front-end complexity — deeply nested, highly variable data needs across different client types — genuinely justifies the added query-complexity cost. Not by default. gRPC is an internal-infrastructure decision, not a public-API one.
Design fundamentals that hold regardless of style
Most modern APIs expose their limits directly in response headers. The engineers who only check these on a 429 are doing it wrong — by then, the wall has already been hit. The right practice is to read these on every response, so you can proactively slow down before the limit fires.
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1643723400 Retry-After: 60
One thing worth knowing: the header convention is currently in transition. The X-RateLimit-* prefix most engineers treat as "the standard" is actually legacy — IETF draft-11 (May 2026) defines a real standard using RateLimit and RateLimit-Policy fields instead. Cloudflare adopted the new standard in late 2025. GitHub and Stripe still use the legacy vendor-prefixed headers as of this writing.
Write client code that tolerates this divergence. Check for both rather than hard-coding one header format.
When a 429 fires, there is a correct order of operations. Most implementations get parts of it right. Few get all of it right.
Catch 429 explicitly
Don't let it surface as a generic network failure or an unhandled exception. It needs its own branch.
Read Retry-After first
If the header is present, wait exactly that long. The server is telling you the answer — use it.
If no Retry-After, use exponential backoff with jitter
Start at 1–2 seconds, double each attempt (1s → 2s → 4s → 8s...), add randomised jitter on top of each wait.
Cap retries at 3–5 attempts
Surface a proper error if all retries fail. A silently dropped request is a data-integrity bug waiting to be discovered much later.
If 429s are frequent, the fix is upstream
Audit your actual request volume, add a queue, or reduce polling frequency. Retry logic handles the exception — it should not be relied on to handle a structurally too-high request rate.
Rate limiting vs throttling
Rate limiting sets a hard cap and rejects excess requests outright (429). Throttling is softer — it slows requests down via delay or queuing rather than rejecting them. Rate limiting suits programmatic API access, where the client is expected to implement backoff logic. Throttling suits user-facing endpoints, where a slow response is a better experience than a hard failure.
Circuit breaker
A circuit breaker is the backstop for sustained throttling, not just retry logic. If an API is throttling heavily and consistently rather than briefly, a circuit breaker that stops sending requests entirely for a cooldown period prevents sustained throttling from becoming a full outage on your end.
The four patterns below compose into a stack. Each one removes a category of unnecessary requests or handles the remainder more gracefully. All four together, not any single one in isolation, is what actually moves the needle.
Cache before you call
The single most effective lever. If data doesn't change every second, don't fetch it every second. In-memory caching, Redis, or even simple local storage for anything that updates on a known cadence removes calls entirely rather than just handling them more gracefully.
Batch instead of looping
A loop making 100 individual API calls where a batch endpoint could fetch the same data in one call is both slower and burns quota unnecessarily. Check whether the API you are using offers a batch or bulk endpoint before defaulting to per-item calls.
Weight expensive operations
Some providers charge different costs for different operation types — GitHub is the commonly cited example, where writes cost more quota than reads. Understand your specific provider's actual cost model. Optimising against the wrong cost model wastes effort.
Use the higher-tier auth method
GitHub Apps get materially higher rate limits than personal access tokens (15,000 requests/hour vs 5,000) for the exact same underlying work. Check whether your provider offers a higher-throughput auth path before assuming your current limit is fixed.
Monitor proactively, not reactively. Track quota usage against known limits before you hit them, with alerting on a real threshold — 80% of quota consumed, for example. This is the difference between catching a problem in a dashboard and catching it in a production incident.
GraphQL as a BFF over REST
Rather than replacing REST outright, a common 2026 pattern uses GraphQL specifically as an aggregation layer in front of several REST services, giving front-end clients precise field selection without requiring every underlying service to be rewritten in GraphQL.
gRPC for internal, high-throughput work
If you're building service-to-service communication you fully control on both ends, gRPC's strong typing and performance profile are a genuine upgrade over REST for that specific internal use case — even while REST remains the right public-facing choice.
tRPC for single-team TypeScript stacks
Worth knowing about specifically because it removes an entire category of API-contract-drift bugs — client and server share actual types, not just a documented schema — when the team building both ends is the same team.
MCP as a genuinely different consumption model
Rather than a human-facing API a developer calls from code, MCP exposes tools to an AI agent directly, with the protocol handling auth, discovery, and interaction patterns in a standardised way. This is not a replacement for REST, GraphQL, or gRPC — it is a parallel consumption layer specifically for AI-agent access, and it is the fastest-growing alternative in the sense that it is genuinely new rather than a repackaging of an older idea.
The header that tells you how close you are to the wall has been there the whole time. The four-pattern stack is not complicated — it is just the discipline of reading it before the 429 fires, and knowing what to do when it does.
Brendan Burns · O'Reilly Media
Patterns and idioms for container-based distributed systems, covering rate limiting, load balancing, and fault tolerance.
Stripe Engineering Blog
How Stripe implements token-bucket rate limiting in production, with the math behind Generic Cell Rate Algorithm.
Martin Abbott & Michael Fisher · Addison-Wesley
A practical framework for scaling people, process, and technology — including quota and throttle design at each scale dimension.
Continue the conversation
If this changed how you think about it — or you think I'm wrong — I want to know.
Corrections, disagreements, and applications all welcome. Replies go directly to Chris.
Get in touch →BEFORE THE 429
~6-8 min1× · Two speakers · tap to play