← Articles·Engineering

BEFORE / THE 429.

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.

15 August 2026·10 min read·
APIRate LimitingRESTGraphQLBackoff

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.

Key Takeaways
  • Read rate limit headers on every response, not just on a 429. By the time a 429 fires, the wall has already been hit. Proactive slowdown prevents the limit from firing at all.
  • IETF draft-11 (May 2026) defines a real standard using RateLimit and RateLimit-Policy fields. Most APIs still use legacy X-RateLimit-* prefixes. Write client code that tolerates both.
  • Full Jitter combined with exponential backoff spreads retry storms across time, reducing total calls by 50%+ compared to fixed-interval retries under the same load.
  • REST is the safe default for public or partner-facing APIs. Reach for GraphQL only when deeply nested, variable data needs across different client types genuinely justify the added query complexity.
01

CHOOSING THE RIGHT STYLE BEFORE YOU START

#

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.

API STYLE — PICK BY USE CASE, NOT BY DEFAULTRESTBEST FORPublic /partner-facing APIs.Safe default foranything external.TRADE-OFFCan over- orunder-fetch vs whatthe client actuallyneeds.GraphQLBEST FORFront-end teamsneeding flexiblequeries acrossdeeply nested data.TRADE-OFFCaching is genuinelyharder — singleendpoint defeatsURL-based CDN.gRPCBEST FORInternal,high-throughputservice-to-servicewhere you own bothends.TRADE-OFFNot browser-native.Wrong fit for anypublic-facing API.tRPCBEST FORSingle TypeScriptteam owning bothclient and server.TRADE-OFFPoor fit the momentexternal ornon-TypeScriptclients need access.← safe default

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

  • Resources are nouns, not verbs. /users not /getUsers. HTTP methods already express the action.
  • Version deliberately. Breaking changes go in a new version path (/v2/), with the old version kept live on a communicated deprecation timeline.
  • Paginate with cursors, not offsets, for any dataset that grows or changes frequently. Offset pagination breaks when rows shift mid-page.
  • Return structured errors that explain what went wrong, not just that something failed. RFC 9457 (Problem Details for HTTP APIs) is the current standard worth adopting.
02

READING THE WALL BEFORE YOU HIT IT

#

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.

03

THE STANDARD HANDLING PATTERN, IN ORDER

#

When a 429 fires, there is a correct order of operations. Most implementations get parts of it right. Few get all of it right.

1

Catch 429 explicitly

Don't let it surface as a generic network failure or an unhandled exception. It needs its own branch.

2

Read Retry-After first

If the header is present, wait exactly that long. The server is telling you the answer — use it.

3

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.

4

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.

5

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.

BACKOFF STRATEGY — WHY JITTER MATTERSEXPONENTIAL BACKOFF — NO JITTERAll clients retry simultaneously → thundering herd1s2s4s8sFULL JITTER BACKOFFRandom spread within window → load distributed1s2s4s8sAWS research: Full Jitter reduced total call count by 50%+ vs non-jittered backoff under contention from 100 simultaneous clients

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.

04

GETTING MORE DONE WITHIN THE LIMITS YOU HAVE

#

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.

FOUR-PATTERN STACK — ALL FOUR TOGETHER, NOT ONE IN ISOLATION1CACHEAvoid the call entirelyIf data doesn't change every second, don't fetch it every second.2BATCH + QUEUEConsolidate and spreadOne batch call beats 100 individual ones. Spread non-urgent requests evenly across the window.3READ HEADERSKnow your position before you're rejectedX-RateLimit-Remaining on every response — not just 429s. IETF draft-11: RateLimit / RateLimit-Policy (2026).4BACKOFFJittered · Capped · Circuit-breaker backstopRetry-After first. Exponential + jitter if absent. Cap at 3–5 attempts. Circuit breaker for sustained throttling.

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.

05

ALTERNATIVES WORTH KNOWING ABOUT

#

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.

Quick reference
CacheAvoid making a call at all for data that does not need to be fresh.
Batch / QueueConsolidate and spread remaining calls rather than bursting.
Read headersKnow your real-time position against the limit before you are rejected.
Backoff (jittered, capped)Handle rejection gracefully, without making the underlying problem worse.
Circuit breakerStop sending entirely during sustained throttling rather than retrying in a loop.

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.

Recommended Reading

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 →
Field Notes · PodcastHost + Expert · Gemini TTS

BEFORE THE 429

~6-8 min

1× · Two speakers · tap to play