Rate Limiting: Token Bucket vs Sliding Window
Rate limiting is what keeps a public API alive: it stops one caller from hogging resources, blocks brute force and scraping, and keeps the service fair. But "rate limit" isn't a single technique — the algorithm you choose determines how bursts are handled, how strict the limit feels, and how much memory it uses.
This guide compares the three most common approaches — token bucket, fixed window, and sliding window — and helps you choose.
Why the algorithm matters
A naive "X requests per minute" has a hidden problem: bursts at window boundaries. If you count in fixed one-minute windows, a client can fire a burst at the end of one window and again at the start of the next, doubling their rate without technically exceeding your limit. The algorithm is how you control that.
Token bucket
How it works: think of a bucket that holds a number of "tokens." Each request consumes a token. Tokens refill at a steady rate. If the bucket is empty, requests are rejected until it refills.
- Allows bursts — if the bucket was full, a burst of requests can pass.
- Steady refill — over time, the rate is smooth.
- Memory-light — only need to track tokens + last refill time per key.
Best for: most APIs where you want to allow occasional bursts (e.g. a spike during a campaign) while capping sustained usage. This is a very common default.
Trade-off: bursts are allowed up to bucket capacity, so if you need a hard, even ceiling, it's less strict.
Fixed window
How it works: divide time into fixed windows (e.g. per minute). Count requests in the current window; reject when the count hits the limit; reset at the window boundary.
- Simple — easy to implement.
- Memory-light — one counter per key per window.
Trade-off: the boundary problem — a client can burst at the end of one window and the start of the next, effectively doubling throughput. Simple to code, but weak against burst abuse.
Sliding window
How it works: instead of fixed windows, use a rolling time window — e.g. "requests in the last 60 seconds." You track timestamps (or use weighted counts of overlapping windows) and reject once the count in the rolling window exceeds the limit.
- Strict, even limit — no boundary double-spend.
- Fair — a client can't exploit window edges.
Trade-off: more memory (tracking timestamps) and slightly more complexity than a fixed window.
Comparing them
| Algorithm | Bursts | Strictness | Memory | Complexity | Best for |
|---|---|---|---|---|---|
| Token bucket | Allows (bounded) | Medium | Low | Low | Most APIs, burst-friendly |
| Fixed window | Allows boundary spikes | Low | Low | Very low | Simple prototypes, non-critical |
| Sliding window | Minimal | High | Higher | Medium | Strict, even limits; sensitive endpoints |
Choosing for your use case
- General API access → token bucket. It allows reasonable bursts (a user clicking quickly) while capping sustained use. Light on memory.
- Login / brute-force protection → stricter limits. Per-account and per-IP limits with lockout; a sliding window or a small fixed window with escalating backoff works well. You want to block bursts, not allow them.
- Resource-heavy endpoints → strict limits (sliding window) so a burst can't hammer an expensive operation.
A real service often layers these: token bucket for general usage, plus tighter per-IP and per-account limits on sensitive actions like login, signup, and link creation.
How this applies to a URL shortener
Shorteners are a target for abuse: automated link creation, brute-forcing custom codes, scraping, and spam. A good shortener rate-limits:
- Per IP — cap requests to stop a single source hammering you.
- Per user/account — cap operations (e.g. link creation per minute) so a compromised or malicious account can't flood.
- On sensitive endpoints — stricter limits on login/register than on read-only lookups.
By choosing token bucket for general use and strict per-IP/per-user limits for sensitive actions, you keep real users happy while stopping abuse.
The takeaway
Rate limiting isn't one thing — the algorithm decides how bursts behave. Token bucket for burst-friendly general access, sliding window when you need a strict, even limit, and fixed window only where simplicity beats strictness. Layer per-IP and per-user limits on top, and you protect the service without frustrating real users.
See how idempotency keys pair with rate limiting to make retries safe, and how API keys scope automated access.
