Reliability & Resilience

Rate-Limiting Algorithms

Rate limiting caps how many requests a client — or the whole system — can make in a time window, protecting downstream capacity and giving predictable, fair pushback instead of an uncontrolled pile-up. Token bucket, leaky bucket, fixed window, and sliding window each make a different precision/memory tradeoff.
  • Token bucket: tokens refill at a steady rate up to a cap; a request consumes one token — allows bursts up to the bucket size, then throttles to the refill rate
  • Leaky bucket: requests queue and drain at a fixed rate, smoothing bursts into constant output rather than allowing them through
  • Fixed window: simplest to implement (a counter reset every window) but can let through nearly the limit right at a window boundary
  • Sliding window (log or weighted counter) fixes the boundary-burst problem at the cost of more memory or computation per check
  • Rate limiting is applied per-client (fairness), globally (protect a shared resource), or both — often at multiple layers: an API gateway and the service behind it
Rate-limiting algorithms compared
AlgorithmAllows bursts?Memory per keyBoundary problem
Fixed windowYes, within a windowOne counterUp to 2× limit at window edges
Sliding window logSmoothly limitedOne timestamp per requestNone, but memory scales with request rate
Sliding window counterSmoothly limited (approximate)Two countersSmall approximation error, cheap
Token bucketYes, up to bucket sizeOne counter + timestampNone — bucket size is the burst allowance
Leaky bucketNo — output is constant-rateA queueNone, but adds queueing latency
Token bucket
class TokenBucket {
    private final long capacity;
    private final double refillPerMs;
    private double tokens;
    private long lastRefillMs;

    TokenBucket(long capacity, double refillPerSecond) {
        this.capacity = capacity;
        this.refillPerMs = refillPerSecond / 1000.0;
        this.tokens = capacity;
        this.lastRefillMs = System.currentTimeMillis();
    }

    synchronized boolean tryConsume() {
        long now = System.currentTimeMillis();
        tokens = Math.min(capacity, tokens + (now - lastRefillMs) * refillPerMs);
        lastRefillMs = now;
        if (tokens >= 1) {
            tokens -= 1;
            return true;
        }
        return false;    // rate limited
    }
}
Fixed window's boundary-burst problem
A client sending N requests at the end of one window and N more at the start of the next slips past a fixed-window limit
Sources
  • System Design Interview – An Insider's GuideCh. 4 — Design a Rate Limiter
  • System Design: The Big Archive (2024 ed.)Ch. — Rate Limiting Algorithms