Yashveer Singh
Connect
<- All posts
Backend, APIs, and System Design6 min read

Rate Limiting Algorithms Compared: Token Bucket, Leaky Bucket, Fixed Window

Rate limiting is the practice of restricting how many requests a client can make to an API within a time window. The algorithm used determines how requests are counted, when limits are enforced, and how burst traffic is handled. Common algorithms include fixed window (simple count per time period), sliding window (count across a rolling time period), token bucket (tokens regenerate at a fixed rate, bursts are allowed up to bucket capacity), and leaky bucket (requests are queued and processed at a fixed rate, smoothing burst traffic).

Written by Yashveer Singh, founder of Yashveer Labs.

What you need to know

  • Fixed window rate limiting is simple to implement but vulnerable to boundary bursts. Sliding window or token bucket prevents the boundary exploit.
  • Token bucket is the most commonly correct choice for public-facing APIs: it allows legitimate burst traffic while enforcing a steady-state rate.
  • Rate limit state must be shared across all instances in a distributed system. Local in-memory counters produce per-instance limits instead of per-user limits.
  • Return standard rate limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) so API clients can adapt their behavior rather than hitting limits blindly.
  • Rate limits should be set by use case, not by system capacity. Distinguish between authenticated users, unauthenticated users, and internal services, each with different limits.

The core argument

Rate limiting is one of the features teams add quickly without thinking through the algorithm choice, then regret later when they discover their implementation allows boundary bursts or penalizes users unfairly. The algorithm choice is not academic. A fixed window rate limiter that resets at the top of every minute creates a predictable exploitation pattern where clients can double their effective rate by timing requests to the window boundary. A token bucket that regenerates tokens too slowly frustrates users who make legitimate but bursty requests.

The right framing for rate limit algorithm selection is: what traffic pattern do you want to accommodate? If the answer is steady traffic with occasional short bursts (the pattern for most interactive API usage), token bucket is correct. The bucket accumulates tokens during quiet periods and allows burst consumption when needed, while enforcing the steady-state rate over time. If the answer is processing requests at a fixed rate regardless of how they arrive (payment processing, email sending, message publishing), leaky bucket is correct. It queues excess requests and processes them at the configured rate.

For most B2B SaaS APIs I have built, token bucket per-user has been the right choice. The implementation on top of Redis is straightforward: store the last request timestamp and current token count per user key, regenerate tokens based on elapsed time since the last request, and deduct tokens atomically on each request. The sliding window log algorithm (storing each request timestamp in a sorted set with TTL) produces more accurate per-window counts but requires more Redis memory for high-volume users.

Common mistakes

  1. Implementing rate limiting in application code without shared state. In-memory rate limiters on individual application servers produce per-server limits rather than per-user limits. A user hitting three different instances can exceed the intended limit by 3x. Rate limit state belongs in Redis or another shared store.
  1. Setting the same limit for all API consumers. An authenticated enterprise customer making bulk API calls for a legitimate integration has different usage patterns than an unauthenticated scraper. Differentiate rate limits by authentication status, user tier, and endpoint type rather than applying a single global limit.
  1. Not returning informative error responses on limit exceeded. A 429 response without a Retry-After header or rate limit headers forces clients to implement arbitrary backoff. The Retry-After header tells clients exactly when they can retry. Good 429 responses produce well-behaved clients; opaque 429 responses produce aggressive retry loops.
  1. Rate limiting at the application layer when the gateway layer can do it. Application-layer rate limiting consumes server resources for requests that should be rejected before reaching the application. API gateways (Kong, Nginx, Cloudflare) can enforce rate limits at the infrastructure layer, returning 429 responses before the request touches application code.
  1. Not testing the rate limiter under burst conditions. A rate limiter that works correctly under steady traffic may fail under burst conditions due to race conditions in the token regeneration logic. Test with concurrent requests at the limit boundary to confirm the atomic operations work correctly under load.

Where to start

  1. Choose the algorithm based on the traffic pattern you want to allow. For interactive API users who make bursty requests: token bucket with a capacity of 2 to 5x the per-second steady-state rate. For background job processing with consistent rate requirements: leaky bucket. For simplicity at low scale: sliding window counter (avoids fixed window boundary issues without full token bucket complexity).
  1. Implement rate limit state in Redis with atomic operations. For token bucket: use a Lua script to atomically read the current token count, regenerate tokens based on elapsed time, deduct the request cost, and write back the new state. The atomicity prevents race conditions where two simultaneous requests both read the same token count and both succeed when only one should.
  1. Add rate limit headers to all API responses, not just 429 responses. Clients that can see their remaining request budget make better decisions about request pacing. A client that sees RateLimit-Remaining: 5 on a non-error response will slow down before hitting the limit. A client with no visibility into their budget hits the limit unexpectedly and retries aggressively.

Related reading

FAQ

Frequently asked

Author

Why you should hire Yashveer Singh for this

The kind of work this article describes is the kind of work I do every week. Production deployments, scaling decisions, the architecture choices that compound over years. I am Yashveer Singh, founder of Yashveer Labs. If you need this done, I do not need to be sold on the brief. Send me what you have and I will tell you what it actually takes.

Related reading