BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Glossary
  4. What Is Rate Limiting? (429, Algorithms, and Retry)
Glossary

What Is Rate Limiting? (429, Algorithms, and Retry)

Rate limiting caps how many requests a client can make in a time window, protecting a service from overload, abuse, and runaway cost. Here is what a 429 means, the algorithms behind the limit, and how a well-behaved client should back off.

Hrishikesh BaidyaHrishikesh Baidya·Jul 16, 2026·8 min read
Glossary
Isometric line-art timeline of successive requests hitting a rate limit and the last one rejected with a 429, cracked, lime on a dark canvas
TL;DR
  • Rate limiting caps how many requests a client can make in a time window, protecting a service from overload, abuse, and runaway cost.
  • Over-limit requests are rejected, typically with HTTP 429 Too Many Requests and a Retry-After header.
  • The common algorithms are fixed window, sliding window, token bucket (the popular one), and leaky bucket.
  • Limits apply per API key, IP, user, or endpoint, and are advertised via X-RateLimit-Limit/Remaining/Reset.
  • A well-behaved client respects Retry-After and uses exponential backoff with jitter — and makes writes idempotent so retries are safe.

Definition

Rate limiting caps how many requests a client can make in a time window, protecting a service from overload, abuse, and runaway cost. Requests over the limit are rejected — typically with HTTP 429 Too Many Requests and a Retry-After header telling the client how long to wait before trying again.

Every shared service has a ceiling: a finite number of requests it can serve per second before latency climbs and things fall over. Rate limiting is the guardrail that keeps traffic under that ceiling by counting each client's requests and refusing the ones that exceed an agreed quota — say, 100 requests per minute per API key. The refused request is not queued or silently dropped; the server answers it immediately with a status code that means “you are going too fast.” On the web that code is HTTP 429 Too Many Requests.

The key word is window. A limit is always “N requests per unit of time,” and both halves matter — 100/minute and 6000/hour are very different constraints even though they average the same. When you hit the cap, you are not blocked forever; you are blocked until the window resets or refills. That is why a 429 is a temporary, recoverable condition, unlike a 403 that says you may never do this. Understanding rate limiting is really about understanding two things: how the server decides you are over the line (the algorithm) and how it tells you to wait (the response).

Why it exists

Rate limiting exists for four overlapping reasons. Fair usage: a shared API has many callers, and without a cap one aggressive client can consume all the capacity and starve everyone else. Abuse and DoS protection: limits blunt brute-force login attempts, scraping, and denial-of-service floods by making high-volume attacks hit a wall. Cost control: when each request costs money — compute, a downstream paid API, an LLM token bill — an unbounded client is an unbounded invoice, and a limit is a budget. Stability under spikes: traffic is bursty, and a limit smooths the peaks so a sudden surge degrades gracefully into 429s instead of taking the whole service down for everyone.

Put simply, rate limiting trades a little availability for a lot of reliability. A caller who gets a 429 and retries a moment later has a worse experience than instant success — but a far better one than a total outage caused by an overloaded server. That is the same tradeoff that shows up in an error budget: you accept a bounded amount of controlled failure to protect the system's overall health.

The 429 response and Retry-After

A 429 is part of the 4xx client-error family: the request was well-formed, but you exceeded your quota, so the fault is on the caller's side, not a server crash. It was standardised in RFC 6585, which added 429 precisely so servers had a clear, machine-readable way to say “slow down” instead of overloading a generic 400 or 503.

The most important thing a good 429 carries is the Retry-After header. It tells the client exactly how long to wait — either a number of seconds (Retry-After: 30) or an absolute HTTP date. A well-behaved client reads that value and waits at least that long before retrying; ignoring it and re-sending immediately is what turns a brief throttle into a self-inflicted outage. Alongside it, many APIs advertise the current state with a trio of headers so you never have to guess: X-RateLimit-Limit (your ceiling), X-RateLimit-Remaining (how much you have left in this window), and X-RateLimit-Reset (when the window refills). Reading those proactively lets you slow down before you get a 429 at all.

The algorithms, explained simply

Servers enforce limits with one of a handful of counting strategies, and the choice shapes how bursty traffic is treated.

  • Fixed window: count requests in each clock-aligned window (e.g. per calendar minute) and reject once the counter passes the limit; the counter resets to zero at the boundary. Simple, but it allows a double-rate burst straddling the boundary — 100 requests at 0:59 and another 100 at 1:00.
  • Sliding window: the same idea, but the window moves continuously with time (or weights the previous window) so there is no hard reset edge to exploit. Smoother and fairer, slightly more state to track.
  • Token bucket: the common one. A bucket holds up to N tokens; tokens refill at a steady rate, and each request spends one token. Requests are served while tokens remain and rejected when the bucket is empty. It permits a short burst up to the bucket size while capping the sustained average at the refill rate.
  • Leaky bucket: requests enter a queue that drains (“leaks”) at a constant rate; if the queue is full, new requests overflow and are rejected. It enforces a perfectly smooth output rate, which is ideal when the thing downstream cannot tolerate bursts at all.

These limits are also applied at different scopes, and often several at once. A limit can be per API key (the paying account), per IP address (blunt but effective against anonymous abuse), per user, or per endpoint (an expensive search route gets a tighter cap than a cheap health check). It is common to hit a per-key limit and a per-endpoint limit independently, which is one reason 429s can feel inconsistent from the outside.

How clients should handle a 429

The golden rule is: do not hammer. When you get a 429, respect the Retry-After value if present. If it is absent, fall back to exponential backoff with jitter — start with a short base delay and double it on each successive failure, then add a small random offset so a fleet of clients that all failed at the same instant do not retry in lockstep and cause a second thundering herd. Cap the retries so you eventually surface a real error instead of looping forever.

Retries interact with correctness. Retrying a read is naturally safe, but retrying a write can create a duplicate — a second charge, a double-posted order — if the first request actually succeeded and only its response was lost. That is why safe retries lean on idempotency: attach an idempotency key so the server can recognise a repeat and return the original result instead of performing the action twice. Backoff decides when to retry; idempotency decides whether retrying is safe at all.

retry-on-429.jsjavascript
async function fetchWithBackoff(url, options = {}, maxRetries = 5) {
  let attempt = 0;
  while (true) {
    const res = await fetch(url, options);
    if (res.status !== 429) return res; // success or a non-rate-limit error

    // 1. Respect Retry-After if the server sent it.
    const retryAfter = res.headers.get('Retry-After');
    let waitMs = retryAfter
      ? Number(retryAfter) * 1000
      : null;

    // 2. Otherwise, exponential backoff with jitter.
    if (waitMs == null) {
      const base = 500 * 2 ** attempt;        // 500ms, 1s, 2s, 4s...
      const jitter = Math.random() * 250;     // spread out synced clients
      waitMs = base + jitter;
    }

    if (++attempt > maxRetries) return res;   // give up: surface the 429
    await new Promise((r) => setTimeout(r, waitMs));
    // Only loop on idempotent requests, or send an idempotency key.
  }
}
The honest catch: great for servers, confusing for clients

Rate limiting reliably protects the service, but it is a classic source of confusing, intermittent failures on the client side. A call that works nine times and fails the tenth — always “sometimes,” never reproducibly — is often a 429 from a burst you did not realise you were sending, or a limit shared across a whole team's key. Because it depends on timing and volume rather than input, it slips through unit tests and lands in production as a “it works on my machine” ghost. The fix is almost always client-side backoff, not a server change.

How this shows up in a BugMojo report

This is exactly where the captured state earns its keep. When a user reports “the dashboard sometimes fails to load,” a stack trace alone is nearly useless — the code is fine, the timing is not. A BugMojo capture includes the full network request log, so a burst of calls followed by a red 429 is right there in the timeline. What looked like a random, unreproducible glitch becomes an obvious pattern: the client fired twelve requests in a second, the API returned 429 with Retry-After: 5, and the front-end never backed off — it just showed an error. The same capture pairs that trace with a related failure class, the failed-to-fetch family, so you can tell a throttle apart from a genuine network drop at a glance.

Key takeaway

Rate limiting caps requests per time window to protect a service; over-limit requests come back as 429 with a Retry-After. Learn the algorithms (token bucket is the common one), read the X-RateLimit-* headers so you slow down early, and always handle a 429 with Retry-After first, then exponential backoff with jitter — making writes idempotent so retries are safe. When a “sometimes it fails” bug appears, check the captured network trace for a burst-then-429 before blaming the code.

Turn “it fails sometimes” into a captured 429

BugMojo records the full network trace behind a bug — so an intermittent burst-then-429 shows up as an obvious pattern in the timeline, not a ghost you can never reproduce. It then hands the whole bundle to your AI agent over MCP.

Install the extension

Frequently asked questions

Frequently asked questions

Sources

  1. 429 Too Many Requests — the status returned when a user has sent too many requests in a given time — MDN Web Docs (2026)
  2. Retry-After — response header indicating how long to wait before making a follow-up request — MDN Web Docs (2026)
  3. RFC 6585: Additional HTTP Status Codes — defines 429 Too Many Requests — IETF RFC 6585 (2012)
  4. Rate limiting — strategies for controlling the rate of requests to a network or service — Wikipedia (2026)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • Definition
  • Why it exists
  • The 429 response and Retry-After
  • The algorithms, explained simply
  • How clients should handle a 429
  • How this shows up in a BugMojo report

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.