Skip to main content
Version: 1.1

Choosing an Algorithm

All five algorithms implement the same interfaces, so changing your mind later is a one-line change. The choice comes down to how much burst you will tolerate and how much state you are willing to keep.

At a glance​

AlgorithmBurstsMemory per keyKeyedRedis
Token bucketup to the burst size, then a steady rateconstant✓✓
Leaky bucketup to the capacity, then a steady rateconstant✓—
Fixed windowup to 2× the limit across a window boundaryconstant✓✓
Sliding window logup to the limit at once, never more than the limit in any windowone timestamp per request in the window——
Sliding window countersmoothed approximationconstant✓✓

Recommendations​

Start with the token bucket. It is the right default for public APIs: it lets a well-behaved client make a quick burst of calls (a page load fetching ten resources) while holding the long-run rate to what you set. It is what golang.org/x/time/rate and most API gateways use.

Use a window when the limit is a quota. "1,000 requests per hour" is a window, not a rate. The sliding window counter enforces it without the fixed window's boundary problem and keeps constant memory per key, so it is the better default of the two. Pick the fixed window when you want windows aligned to the clock — the limit resets on the hour — and the boundary burst is acceptable.

Use the sliding window log when exactness matters more than memory. It is the only algorithm with no approximation and no boundary effect, at the cost of storing every request in the window. That is fine for low limits — 5 login attempts per 15 minutes — and wasteful for high ones.

Use the leaky bucket when you think in fill levels. It makes the same decisions as a token bucket with the same rate and size; the difference is which way it counts and that it starts empty. See how they relate.

Going distributed? The Redis store implements the token bucket, fixed window, and sliding window counter. See Redis.

Constructors​

Every algorithm has a plain constructor, and the rate-based ones also take a count per duration, which is usually easier to read:

ratelimit.NewTokenBucket(100.0/60.0, 10) // 100/min, burst 10
ratelimit.NewTokenBucketPerDuration(100, time.Minute, 10) // same thing

ratelimit.NewLeakyBucket(10, 50) // 10/s, capacity 50
ratelimit.NewLeakyBucketPerDuration(600, time.Minute, 50) // same thing

ratelimit.NewFixedWindow(1000, time.Hour)
ratelimit.NewSlidingWindow(5, 15*time.Minute) // log
ratelimit.NewSlidingWindowCounter(100, time.Minute) // counter

Each except the sliding window log has a keyed counterpart that takes an extra cleanup interval — see Keyed Limiters.

Constructors panic on arguments that cannot form a valid limiter: a rate, burst, capacity, limit, or window that is zero or negative. These are programming errors caught at startup, not runtime conditions.