Rate Limiting Algorithms

Production-grade guide to rate limiting algorithms covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

Rate limiting algorithms regulate how many requests a system accepts within a defined time window, preventing overload, resource exhaustion, and service degradation. They are essential in distributed systems where clients — from mobile apps to microservices — consume APIs at varying rates, and where backend services have finite capacity. This is especially critical under high load, during traffic spikes, or when external services impose quotas, such as in cloud infrastructure, payment gateways, or third-party data providers.

When to Apply Rate Limiting

Rate limiting is not a one-size-fits-all solution. Choose it when:

Choosing a Rate Limiting Algorithm

The core trade-off is between simplicity, accuracy, and memory efficiency. The right algorithm depends on whether you need:

Fixed Window Algorithm

A fixed window divides time into equal intervals (e.g., 1 minute) and resets a counter at the start of each interval.

Request: GET /api/users/123
Headers:
  Authorization: Bearer abc123
  X-RateLimit-Key: user-123

At the start of each minute, the system resets a counter for user-123 to zero. Each request increments the counter. When the counter reaches the limit (e.g., 100 requests per minute), subsequent requests receive a 429 Too Many Requests response.

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60

{
  "error": "Rate limit exceeded",
  "limit": 100,
  "remaining": 0,
  "reset": 1710000000
}

The X-RateLimit-* headers convey the current state:

Key failure modes:

Configuration example (Redis-based):

rate_limiting:
  algorithm: fixed-window
  window_size: 60s
  max_requests: 100
  cache:
    type: redis
    host: redis.example.com
    port: 6379
    key_prefix: "rl:fixed:"

Sliding Window Algorithm

The sliding window tracks individual request timestamps and computes the number of requests in the last N seconds.

For each request, the system:

  1. Records the request timestamp.
  2. Filters all recorded timestamps to those within the last window_size seconds.
  3. Returns the count of requests in that window.
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1710000000

{
  "id": 123,
  "name": "Alice"
}

Implementation with Redis (using sorted sets):


redis-cli ZADD rl:sliding:user-123 1709999900.123456 {request_id}
redis-cli ZREMRANGEBYSCORE rl:sliding:user-123 1709999900.123456 -inf (1709999900.123456 - 60)
redis-cli ZCARD rl:sliding:user-123

The ZCARD command returns the number of requests in the current window. The ZREMRANGEBYSCORE ensures only requests from the last 60 seconds remain.

Common pitfalls:

Leaky Bucket Algorithm

The leaky bucket enforces a constant output rate. Requests are queued in a buffer (the bucket), and the system outputs requests at a fixed rate (e.g., 10 requests per second), with the bucket draining at a fixed rate.

Data flow:

Implementation with a queue (e.g., RabbitMQ or Kafka):

{
  "queue": "rate-limiting-leaky",
  "max_capacity": 100,
  "drain_rate": 10,
  "drain_interval": 1.0
}

Each incoming request is published to the queue. A worker process continuously polls the queue and processes one request every 1 / drain_rate seconds.

Error response when the bucket is full:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 10

{
  "error": "Leaky bucket full",
  "bucket_capacity": 100,
  "current_fill": 100,
  "drain_rate": 10,
  "next_available": 1710000015
}

Key characteristics:

Failure modes:

Token Bucket Algorithm

The token bucket allows flexible, bursty traffic. Tokens are added to a bucket at a fixed rate. Each request consumes one token. If tokens are available, the request is accepted; otherwise, it is rejected or queued.

Data flow:

Implementation using Redis:

# On request arrival:
redis-cli INCRBY rl:token:api-key-abc123 1
redis-cli EXPIRE rl:token:api-key-abc123 60
redis-cli GET rl:token:api-key-abc123

The INCRBY command adds one token (or increments the counter). The EXPIRE ensures the key is removed after the window.

Response when token is available:

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 98
X-RateLimit-Reset: 1710000000

When no tokens are available:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 5

{
  "error": "Token bucket depleted",
  "limit": 100,
  "remaining": 0,
  "next_available": 1710000005
}

Configuration:

rate_limiting:
  algorithm: token-bucket
  initial_tokens: 10
  fill_rate: 10
  window_size: 60s
  cache:
    type: redis
    key: "rl:token:{key}"

Subtle points:

Selecting the Right Algorithm

GoalAlgorithmWhy
Simple, fast, low memoryFixed windowEasy to implement, ideal for edge services
Accurate burst detectionSliding windowPrevents window boundary overflows
Smooth, predictable outputLeaky bucketIdeal for streaming, real-time systems
Flexible bursts and average rateToken bucketBest for APIs with varied client behavior

Common Patterns and Pitfalls

Error Handling and Response Contracts

All rate limiters return a standard set of headers:

When a request exceeds the limit, the response body includes:

{
  "error": "Rate limit exceeded",
  "details": {
    "limit": 100,
    "remaining": 0,
    "reset": 1710000000,
    "window": "minute",
    "type": "fixed-window"
  }
}

Failure modes:

Summary

Rate limiting algorithms are the backbone of API resilience. The choice between fixed window, sliding window, leaky bucket, and token bucket depends on the required accuracy, burst tolerance, and resource constraints. Each algorithm has subtle behaviors and failure modes that are not immediately apparent. Implementing them effectively requires attention to response contracts, error handling, and fallback strategies. The real challenge lies not in applying an algorithm, but in understanding the trade-offs and making them visible through consistent, predictable responses.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.