Debounce, Throttle, Rate Limit and Friends: 18 Flow-Control Patterns in Simple English
Software often gets more work than it should do right now. Too many clicks, too many requests, a slow service, a sudden spike. There is a small set of patterns for this. Each one answers a simple question: how much work should run, and when?
Index
18 patterns in 4 groups. Click any pattern to jump to it.
| # | Pattern | The idea in one line |
|---|---|---|
| 1. Too Many Events | ||
| 01 | Debounce | Wait until things go quiet. |
| 02 | Throttle | At most once every X. |
| 03 | Batching | Collect, then send together. |
| 04 | Sampling | Keep only some of the events. |
| 05 | Coalescing | Same question? Ask once. |
| 06 | Deduplication | Seen it before? Ignore it. |
| 2. Too Many Requests | ||
| 07 | Rate Limiter | You get N per time window. |
| 08 | Quota | A long-term budget. |
| 09 | Concurrency Limit | Only N at the same time. |
| 3. System Under Pressure | ||
| 10 | Buffering | Hold it until the other side is ready. |
| 11 | Queueing | Get in line. |
| 12 | Backpressure | Tell the sender to slow down. |
| 13 | Load Shedding | Drop the least important work first. |
| 4. When Things Fail | ||
| 14 | Timeout | Do not wait forever. |
| 15 | Retry with Backoff | Try again, but wait longer each time. |
| 16 | Jitter | Add a little randomness. |
| 17 | Circuit Breaker | Stop calling a broken service for a while. |
| 18 | Fallback | Have a plan B. |
| Extra: Cheat Sheet · How They Work Together | ||
Each pattern has a one-line idea, a short explanation, and a diagram. In the diagrams:
Too Many Events
The user or the system fires events faster than you need to handle them. These patterns do less work.
Debounce · Throttle · Batching · Sampling · Coalescing · Deduplication
Debounce
"Wait until things go quiet."Every new event restarts a timer. The action runs only after events stop for a set time. If events may never stop (like auto-save while typing), also set a maximum wait, or it will never run.
Use it for: Search-as-you-type, auto-save, window resize.
↑ Back to indexThrottle
"At most once every X."The action runs at most once per time window. Events that arrive in between are skipped. Most tools also run once more at the end of a burst, so the last value (like the final scroll position) is not lost.
Use it for: Scroll and mouse-move handlers, progress updates, game input.
↑ Back to indexBatching
"Collect, then send together."Small items go into a buffer. When it is full, or a timer fires, all items go out in one call.
Use it for: Sending logs and metrics, bulk database inserts, analytics events.
↑ Back to indexSampling
"Keep only some of the events."Handle a small share of events and skip the rest. The share can be fixed (1 in N) or random.
Use it for: Tracing, debug logs, analytics on very busy systems.
↑ Back to indexCoalescing
"Same question? Ask once."When several callers ask for the same thing at the same time, make one real call and give every caller the same answer. This avoids a "thundering herd". It is not caching: it shares a call that is still running, while a cache stores finished answers.
Use it for: Cache misses, popular API calls, loading the same file many times.
↑ Back to indexDeduplication
"Seen it before? Ignore it."Give each request a unique key. Remember keys you have handled, and skip any key you have already seen. Keep keys for a limited time, or the list grows forever.
Use it for: Double-clicked "Pay" buttons, retried webhooks, message queues that deliver twice.
↑ Back to indexToo Many Requests
Someone asks for more than you want to give. These patterns set a budget.
Rate Limiter · Quota · Concurrency Limit
Rate Limiter
"You get N per time window."Cap how many requests a user, key or IP can make in a time window. Extra requests usually get 429 Too Many Requests, often with a Retry-After header. The diagram shows a token bucket: it refills at a steady rate, and a full bucket lets a short burst through.
Use it for: Public APIs, login attempts, protecting paid third-party services.
↑ Back to indexQuota
"A long-term budget."Like a rate limit, but over a long period, such as a day or a month. Often tied to a plan or a price. When it runs out, APIs usually return 429 or 403.
Use it for: Free vs. paid API plans, storage limits, monthly AI token budgets.
↑ Back to indexConcurrency Limit
"Only N at the same time."Limit how many jobs run at once. New jobs wait for a free slot. Usually built with a semaphore. A bulkhead goes one step further: each dependency gets its own limit, so one slow service cannot use up every slot.
Use it for: Database connection pools, calls to a slow service, CPU-heavy work.
↑ Back to indexSystem Under Pressure
Work arrives faster than you can finish it. These patterns absorb, pace or cut the load.
Buffering · Queueing · Backpressure · Load Shedding
Buffering
"Hold it until the other side is ready."Keep data in memory between a producer (sends data) and a consumer (reads data) that work at different speeds. A buffer smooths out the speed difference. Keep it a fixed size, or it can fill up memory.
Use it for: Video streaming, file reading and writing, network sockets.
↑ Back to indexQueueing
"Get in line."Put jobs in a first-in, first-out (FIFO) list. Workers take jobs from the front at their own speed. A buffer smooths speed; a queue keeps order and hands work to workers, often for later.
Use it for: Sending emails, image processing, background jobs.
↑ Back to indexBackpressure
"Tell the sender to slow down."When the receiver's queue is full, it signals the sender to pause or slow down, so memory does not fill up. Some systems drop or block instead of pausing.
Use it for: Streams, message brokers, data pipelines.
↑ Back to indexLoad Shedding
"Drop the least important work first."When the system is overloaded, reject low-priority requests before they use up resources, so the important ones still work. This usually returns 503, often with Retry-After, and clients should wait before trying again.
Use it for: Keeping checkout alive during a sale, protecting core APIs during spikes.
↑ Back to indexWhen Things Fail
Other services will be slow or down sometimes. These patterns fail safely.
Timeout · Retry with Backoff · Jitter · Circuit Breaker · Fallback
Timeout
"Do not wait forever."Set a deadline for every call. If there is no answer in time, give up and return an error. The other side may still finish the work, so be careful when you retry.
Use it for: Every network call, database query and external API.
↑ Back to indexRetry with Backoff
"Try again, but wait longer each time."Many errors are short. Retry them, but double the wait after each failure so you do not overload a service that is struggling. Always set a maximum number of tries and a maximum wait. Only retry actions that are safe to repeat (idempotent): reads, or writes with an idempotency key (see Deduplication).
Use it for: Network blips, rate-limited APIs, brief outages.
↑ Back to indexJitter
"Add a little randomness."If many clients retry on the same schedule, they all hit the server at the same moment. Use a random wait instead of an exact one, for example anywhere between 0 and the backoff time. This spreads them out.
Use it for: Any retry or timer used by many clients at once.
↑ Back to indexCircuit Breaker
"Stop calling a broken service for a while."After too many failures in a row, the breaker opens and calls fail fast without waiting. After a pause it goes half-open: one test call checks if the service is back.
Use it for: Calls to other services and third-party APIs.
↑ Back to indexFallback
"Have a plan B."When the main source fails, answer from a backup: a cache, a default value, or a simpler feature.
Use it for: Showing cached prices, default recommendations, a read-only mode.
↑ Back to indexCheat Sheet
| Pattern | Question it answers | What happens to extra work | Where you usually add it |
|---|---|---|---|
| Debounce | Have they stopped yet? | Resets the timer | UI / client |
| Throttle | Has enough time passed? | Skipped | UI / client |
| Batching | Can I send these together? | Waits for the batch | Client or server |
| Sampling | Do I need all of them? | Skipped | Client or server |
| Coalescing | Is someone already asking this? | Shares one result | Server |
| Deduplication | Did I already do this? | Ignored | Server |
| Rate limiter | Is there budget left right now? | Rejected (usually 429) | Server, at the API edge |
| Quota | Is there budget left this month? | Rejected until reset | Server |
| Concurrency limit | Is there a free slot? | Waits for a slot | Server |
| Buffering | Is the reader ready? | Held in memory | Anywhere |
| Queueing | Can this be done later? | Waits in line | Server |
| Backpressure | Can the receiver keep up? | Sender slows down | Between two services |
| Load shedding | Is this work important? | Dropped (usually 503) | Server |
| Timeout | Is it taking too long? | Cancelled | Caller |
| Retry + backoff | Was it a short error? | Tried again, later | Caller |
| Jitter | Is everyone retrying together? | Spread out | Caller |
| Circuit breaker | Is the service broken? | Fails fast | Caller |
| Fallback | Is there a plan B? | Answered from backup | Caller |
How They Work Together
Real systems mix these patterns. Here is one common way to stack them around a call to an external API, from the outside in:
- Fallback: if everything below fails, show the cached answer.
- Circuit breaker: if the API keeps failing, stop calling it for a while.
- Retry with backoff and jitter: try short errors again, with a maximum number of tries.
- Rate limiter: before each try, stay inside the API's limit.
- Timeout: never wait more than 2 seconds for one try.
Other orders can work too. The key point is that the breaker wraps the retries, so it sees every failed try.
Start simple. Add a timeout to every network call first. Then add the other patterns where you actually see the problem.