Library
7 min read

Rate limiting vs throttling vs backpressure

Published

Every system has a speed it can work at. When work arrives faster than that, something has to give. The only question is what, and these four words are four different answers:

  • Rate limiting: refuse anything over an agreed rate. "This API key gets 100 requests a second; the 101st gets 429 Too Many Requests." The bouncer at a full club.
  • Throttling: slow the caller down instead of refusing. Requests over the rate wait and are served later. The toll plaza that lets cars through one at a time.
  • Backpressure: the overloaded component signals upstream to send less, and the slowdown travels back towards the source. A full kitchen tells the waiters to stop taking orders.
  • Load shedding: when overloaded anyway, deliberately drop some work, the least important first, so the rest still succeeds.

People use "rate limiting" and "throttling" loosely and sometimes interchangeably (AWS calls its 429 style rejections "throttling"). In a design discussion, be precise about whether the excess is rejected, delayed or pushed back.

Your answer

A worker processes 100 orders a second. A flash sale sends 160 a second for ten minutes, into an in memory queue with no size limit. Walk through what happens minute by minute. What would you change?

Watch overload play out#

A worker that handles 100 requests a second
160
OOM5000s30s60s
Waiting at 60 s: crashedRejected: 0Wait for a new request: n/a
60 extra requests a second pile up in an unbounded queue. Latency climbs with it, and at 51 s the process runs out of memory and drops everything, including work it had accepted. This is the most common way overload turns into an outage.

The unbounded queue is the instructive failure. Nothing errors at first. The queue just grows, latency grows with it, clients time out and retry (adding even more load), memory climbs, and then the process dies and loses everything it had accepted. An overloaded system without limits does not degrade gracefully, it falls off a cliff.

Rate limiting: protect yourself and be fair#

Rate limits sit at the edge, usually in an API gateway or a shared middleware, keyed by API key, user, IP or route. They protect your capacity from one noisy caller, enforce pricing tiers and blunt abuse such as credential stuffing. The algorithms (token bucket, sliding window) and the distributed counting problem are covered in design a rate limiter.

Good rate limit responses tell the caller what to do:

http
HTTP/1.1 429 Too Many Requests
Retry-After: 2
RateLimit-Policy: 100;w=1
RateLimit: limit=100, remaining=0, reset=2

Throttling: slow, not refused#

Throttling queues or delays the excess so it is served at the allowed rate. It fits callers that would rather be late than fail: a nightly sync job, a bulk email sender, a mobile app uploading photos in the background. It needs a bound too: a delay queue that grows without limit is just the unbounded queue again, wearing a different name. Client side throttling is common as well: an SDK that spaces out its own calls to stay under a provider's limit.

Backpressure: let the slowdown travel#

Backpressure works by making queues bounded and letting a full queue block or refuse its producer, which then slows down, which fills its own input, and so on up the chain until the original source sends less. Nothing is lost and memory stays capped, because each stage only accepts what it can hold.

It is everywhere once you look. TCP flow control: the receiver advertises how much buffer space it has, and the sender cannot exceed it. Node.js streams: write() returns false when the buffer passes highWaterMark, and pipe() pauses the source until drain. Kafka consumers pull at their own pace, so lag grows in Kafka's durable log instead of in a consumer's memory. Go channels with a buffer size block the sender when full.

ts
// Reads a 20 GB file as fast as the disk allows and pushes it at a slow upload.
// Everything the network cannot take yet piles up in memory.
readStream.on('data', (chunk) => upload.write(chunk))

Load shedding: choose what fails#

Even with limits, a service can be pushed past capacity: a dependency slows down, a cache goes cold, a retry storm hits. Load shedding drops work early and cheaply, before it consumes resources: reject requests that have already waited longer than the client's timeout (nobody is listening for the answer), and drop low priority traffic first. Checkout before recommendations; paying users before free tier; real users before health checks from a partner's monitoring.

Retries turn a slowdown into an outage

When a service slows down, clients time out and retry. If each client retries three times, an overloaded service now receives up to four times the load, exactly when it can least handle it. Retry with exponential backoff and jitter (randomised waits), cap total retries, use a retry budget (at most, say, 10% extra traffic from retries), and add a circuit breaker that stops calling a failing dependency for a while. Respect Retry-After.

ChoiceWhat you gainWhat you payPick it when
Rate limiting (reject)Protects capacity, fair between callers, immediate clear signal (429).Callers see errors and must handle them. Limits need tuning per tier.At the API edge, per key or user. Public APIs always.
Throttling (delay)Callers succeed, just later. Smooths bursts.Latency for the throttled caller; needs a bounded delay queue.Background and batch callers, outbound calls to a rate limited provider.
Backpressure (push back)Nothing lost, memory bounded, the system runs at the speed of its slowest stage.The producer must be able to slow down; can propagate to users as slowness.Pipelines, streams, queues between internal stages.
Load shedding (drop)Keeps the important work alive under overload.Some work fails on purpose; needs priorities defined in advance.The last line of defence for any service that can be overloaded.
Names you will hear
  • KongRate limiting plugins at the gateway
  • RedisShared counters for distributed rate limits
  • EnvoyRate limits, circuit breakers, outlier detection
  • Apache KafkaPull consumers: lag instead of memory blow up
  • CloudflareEdge rate limiting and DDoS protection

In the interview#

Interviewer
Your order service depends on a payment provider that allows 500 requests a second. A sale brings 2,000 orders a second. What happens?
They want several mechanisms in the right places, not just "rate limit".
You
I would accept orders into a durable queue, Kafka or SQS, so the order is saved and the user sees "order placed, confirming payment". A pool of payment workers pulls from it and is throttled to stay under 500 a second, with backoff and jitter if the provider returns 429. The queue absorbs the burst and drains after the sale. At the edge, a per user rate limit stops one client hammering checkout, and if even the queue intake is overwhelmed, I shed non essential traffic like recommendations first.
Durable buffer, throttled consumers, backoff on 429, edge limits, priority shedding.
Which is it?
A free tier key gets 429 after 60 requests in a minute
A TCP receiver advertises a zero window and the sender stops
An email service sends a bulk campaign at 50 a second to stay under the provider's limit
An overloaded search service skips the personalisation step for anonymous users

Checkpoint#

Checkpoint

1. Why is an unbounded in memory queue dangerous under sustained overload?

2. What is the difference between rate limiting and throttling?

3. How can retries make an outage worse?

Worth memorising
  • Rate limit: reject (429 + Retry-After). Throttle: delay. Backpressure: bounded queues push back upstream. Shed: drop low priority first.
  • Unbounded queues turn overload into a crash.
  • Retries need backoff, jitter, budgets and circuit breakers.
  • Backpressure examples: TCP windows, Node stream pipe, Kafka pull.
  • A durable queue plus throttled workers absorbs bursts.
Say this in 60 seconds

When work arrives faster than a system can process it, something must give, and there are four deliberate choices. Rate limiting rejects requests over an agreed rate with a 429 and Retry-After, usually at the gateway per key or user, protecting capacity and enforcing fairness. Throttling delays the excess so it is served later at the allowed rate, suiting background and batch callers, with a bounded delay queue. Backpressure makes every queue bounded so a full stage blocks its producer and the slowdown travels upstream, as TCP flow control, Node stream pipes and Kafka pull consumers do, keeping memory capped and losing nothing. Load shedding drops the least important work first when overloaded anyway. The failure to avoid is an unbounded queue, which hides overload until it crashes, made worse by retry storms, so retries need exponential backoff with jitter, budgets and circuit breakers.