The leaky bucket strategy is a traffic shaper, not a rate limiter in the traditional sense. Rather than denying requests that arrive too fast, it schedules each accepted request at a precisely paced departure time, smoothing bursty input into a steady output stream atDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/AmeyaBorkar/throttlekit/llms.txt
Use this file to discover all available pages before exploring further.
ratePerSec units per second. A request is rejected only when its scheduled departure time would exceed maxQueueMs from now — i.e., the queue is full.
This makes leaky bucket ideal for pacing outbound calls to a third-party API with a strict rate budget, where you want to absorb spikes by queuing rather than shedding, but still need a safety valve for extreme bursts.
The leaky bucket’s next-departure recurrence is mathematically identical to GCRA’s TAT. The key difference: GCRA rejects when the wait exceeds the burst tolerance; the leaky bucket waits. The shaper delays rather than denies, and only denies when the wait exceeds
maxQueueMs.Options
The steady drain rate in units per second — the paced output rate. The pacing interval per unit is
T = 1000 / ratePerSec milliseconds. For example, ratePerSec: 5 paces one departure every 200ms.The maximum time a request may wait in the queue before it is rejected instead of delayed. If the next available departure slot is more than
maxQueueMs milliseconds away, the reservation is rejected with accepted: false. Set to 0 for a pure token-bucket-style drop policy with no queuing.Where the next-departure timestamp lives. Defaults to a fresh in-process
MemoryStore. Provide a RedisStore for a distributed shaper that coordinates across multiple processes.Injectable time source. Defaults to the system clock. Use
ManualClock for deterministic tests.Key namespace prefix. Useful when multiple shapers share the same store.
The Shaper API
The leaky bucket is exposed as aShaper interface rather than a Strategy, because it returns a Reservation (with a delay) rather than a Decision (allow/deny with remaining count). There are three methods:
reserve(key, cost?): Promise<Reservation>
Reserves a slot and returns a Reservation immediately — it never sleeps. The returned object has:
accepted: boolean— whether the slot was granted (i.e., the wait is withinmaxQueueMs).delayMs: number— when accepted, how long to wait before proceeding so the output is paced. When rejected, an advisory estimate of how long until the queue would have room.
reserve when you want to decide whether to sleep yourself (e.g., in a job queue where you enqueue a delayed task).
reserveSync(key, cost?): Reservation
Synchronous version of reserve. Requires a synchronous store (e.g., MemoryStore). Throws a ThrottleKitError if the configured store is async-only.
schedule(key, cost?): Promise<void>
Convenience method: reserves a slot and then sleeps for delayMs. Resolves when the slot’s departure time arrives, or throws QueueFullError if the queue is full. This is the simplest way to pace outbound calls.
reset(key): Promise<void>
Clears a key’s queue position, resetting the departure clock to now.
QueueFullError
When a reservation is rejected because the queue is full,schedule() throws a QueueFullError:
QueueFullError.retryAfterMs is an advisory estimate of how long until the queue drains enough to accept a new reservation.
Code examples
The following examples show the three usage patterns for the leaky bucket shaper: inspect-and-sleep withreserve, automatic pacing with schedule, and deterministic unit testing with reserveSync.
reserve — inspect the delay, sleep yourself
schedule — sleep automatically, then proceed
Deterministic testing with reserveSync
Queue semantics vs reject semantics
The leaky bucket operates in a queue mode by default: it delays rather than denies, and the queue absorbs short bursts. This is fundamentally different from GCRA or token bucket, which operate in reject mode: they give an immediate allow/deny decision with no waiting.| Leaky Bucket | GCRA / Token Bucket | |
|---|---|---|
| On burst | Queues requests, paces output | Rejects requests over the burst |
maxQueueMs | Safety valve — rejects only when queue is full | N/A — reject is immediate |
| Return type | Reservation { accepted, delayMs } | Decision { allowed, remaining, ... } |
| Best for | Outbound traffic shaping | Inbound rate limiting |
When to use leaky bucket
- Pacing outbound API calls — when your service makes calls to a third-party API with a strict rate budget and you want to absorb short bursts by queuing rather than dropping.
- Steady-rate job queues — when you want to dispatch jobs at a controlled rate regardless of how many arrive at once.
- Smooth egress — when the downstream system is sensitive to bursts and you need to regulate the output rate, not just the input count.