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.
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#
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/1.1 429 Too Many Requests
Retry-After: 2
RateLimit-Policy: 100;w=1
RateLimit: limit=100, remaining=0, reset=2Throttling: 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.
// 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.
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.
| Choice | What you gain | What you pay | Pick 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. |
- 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#
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?
- 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.
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.