mdashikjs/blog
All posts
Rate Limiting APIs: Sliding Window vs Token Bucket
System Design

Rate Limiting APIs: Sliding Window vs Token Bucket

System Design5 min

Rate Limiting APIs: Sliding Window vs Token Bucket

Every API needs rate limiting. The question is which algorithm fits your traffic pattern. I have implemented both in production and the tradeoffs are not obvious until you see real traffic.

Rate LimitingSystem DesignAPIRedis
Share:

Fixed window: simple but flawed

Count requests per minute. Reset at the minute boundary. The problem: a user can fire 100 requests at 11:59:59 and another 100 at 12:00:00 — 200 requests in 2 seconds while technically staying within the 100/min limit.

Sliding window log

Store the timestamp of every request. Count how many fall within the last 60 seconds. Accurate but memory-expensive — at scale, storing every timestamp per user is not practical.

Sliding window counter

The practical middle ground. Keep counts for the current and previous window, then weight them:

function isAllowed(userId: string, limit: number, windowMs: number): boolean {
  const now = Date.now();
  const currentWindow = Math.floor(now / windowMs);
  const previousWindow = currentWindow - 1;
  const elapsed = (now % windowMs) / windowMs;

  const prevCount = getCount(userId, previousWindow);
  const currCount = getCount(userId, currentWindow);
  const estimated = prevCount * (1 - elapsed) + currCount;

  return estimated < limit;
}

Two counters per user instead of thousands of timestamps. Close enough to accurate for most APIs.

Token bucket: burst-friendly

If your API should allow short bursts but limit sustained throughput, token bucket is the right choice. Tokens refill at a steady rate. Each request costs one token. If the bucket is empty, the request is rejected.

class TokenBucket {
  private tokens: number;
  private lastRefill: number;

  constructor(private capacity: number, private refillRate: number) {
    this.tokens = capacity;
    this.lastRefill = Date.now();
  }

  tryConsume(): boolean {
    this.refill();
    if (this.tokens < 1) return false;
    this.tokens -= 1;
    return true;
  }

  private refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
    this.lastRefill = now;
  }
}

Which one should you use

  • Sliding window: when you want predictable, evenly distributed throughput. Good for public APIs with per-user quotas.
  • Token bucket: when you want to allow bursts. Good for webhook receivers, batch endpoints, and internal services where traffic is naturally bursty.

Implementation detail: use Redis

Both algorithms work in-memory for a single server. For distributed systems, use Redis. The sliding window counter needs two INCR operations with EXPIRE. The token bucket maps naturally to a Redis hash with a Lua script for atomic refill-and-consume.

MA

Written by Md Ashik

Senior Software Engineer building reliable backends. I write about the practical tradeoffs behind shipping software that holds up in production.