Rate Limiting Algorithms: 5 Implementation Methods from System Design Notes

The liquidslr/system-design-notes repository documents five fundamental rate limiting algorithms—Token Bucket, Leaking Bucket, Fixed Window Counter, Sliding Window Log, and Sliding Window Counter—that balance memory efficiency, burst tolerance, and implementation complexity for distributed systems.

The 04. Rate Limiter/Readme.md file in the system-design-notes repository provides detailed architectural analysis and practical implementations for controlling API traffic. Understanding these rate limiting algorithms is essential for preventing resource exhaustion and maintaining service availability during traffic spikes. Each approach offers distinct trade-offs between precision, memory usage, and burst handling, as illustrated in the accompanying diagrams within 04. Rate Limiter/images/.

Token Bucket Algorithm

The Token Bucket algorithm maintains a bucket with a fixed capacity of tokens that are refilled at a steady rate. According to lines 44-54 of 04. Rate Limiter/Readme.md, every incoming request consumes one token; if the bucket is empty, the system rejects the request immediately.

This method excels at handling short-term traffic bursts while maintaining long-term rate limits. However, operators must carefully tune the bucket size and refill rate parameters to prevent unnecessary rejections or excessive throughput.

Go Implementation

The repository provides a thread-safe implementation using sync.Mutex and time.Ticker to manage concurrent access and token replenishment:

type TokenBucket struct {
    rate      int           // tokens added per interval
    capacity  int           // max tokens
    tokens    int
    ticker    *time.Ticker
    mu        sync.Mutex
}

func NewTokenBucket(rate, capacity int, interval time.Duration) *TokenBucket {
    tb := &TokenBucket{rate: rate, capacity: capacity, tokens: capacity}
    tb.ticker = time.NewTicker(interval)
    go func() {
        for range tb.ticker.C {
            tb.mu.Lock()
            tb.tokens = min(tb.tokens+tb.rate, tb.capacity)
            tb.mu.Unlock()
        }
    }()
    return tb
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()
    if tb.tokens > 0 {
        tb.tokens--
        return true
    }
    return false
}

Leaking Bucket Algorithm

The Leaking Bucket algorithm processes requests through a FIFO queue at a fixed outflow rate. As implemented in lines 57-65 of the source documentation, this approach guarantees steady traffic flow but can delay bursty requests since newer arrivals must wait behind earlier ones in the queue.

While memory-efficient due to fixed queue capacity, this algorithm is less forgiving of traffic spikes compared to the Token Bucket approach.

Go Implementation

The LeakingBucket struct manages a timestamp queue and drains entries based on a fixed interval:

type LeakingBucket struct {
    capacity int
    queue    []time.Time
    interval time.Duration
    mu       sync.Mutex
}

func NewLeakingBucket(capacity int, interval time.Duration) *LeakingBucket {
    return &LeakingBucket{capacity: capacity, interval: interval}
}

func (lb *LeakingBucket) Allow() bool {
    lb.mu.Lock()
    defer lb.mu.Unlock()
    now := time.Now()
    // Remove drained items
    for len(lb.queue) > 0 && now.Sub(lb.queue[0]) >= lb.interval {
        lb.queue = lb.queue[1:]
    }
    if len(lb.queue) < lb.capacity {
        lb.queue = append(lb.queue, now)
        return true
    }
    return false
}

Fixed Window Counter Algorithm

The Fixed Window Counter divides time into discrete windows (e.g., one-minute intervals) and tracks request counts per window. According to lines 71-80 of 04. Rate Limiter/Readme.md, once the counter reaches its limit, all subsequent requests are blocked until the next window begins.

This algorithm offers low computational overhead and minimal memory footprint but suffers from edge-case spikes when bursts straddle two consecutive windows, potentially allowing double the intended traffic volume at boundary transitions.

Redis-Backed Implementation

For distributed systems, the repository demonstrates a Redis-based implementation using atomic increment operations:

func FixedWindowAllow(ctx context.Context, rdb *redis.Client, key string, limit int, window time.Duration) (bool, error) {
    cur, err := rdb.Incr(ctx, key).Result()
    if err != nil {
        return false, err
    }
    if cur == 1 {
        rdb.Expire(ctx, key, window)
    }
    return cur <= int64(limit), nil
}

Sliding Window Log Algorithm

The Sliding Window Log maintains a timestamp log for every request, typically stored in a sorted set. The algorithm evaluates the current request by counting entries within a rolling time interval, as detailed in lines 86-94 of the documentation.

While this approach provides precise rate limiting with true rolling windows, it demands significant memory resources since every request timestamp must be retained for the duration of the window.

Redis Sorted Set Implementation

The SlidingLogAllow function uses Redis sorted sets to maintain and purge timestamps atomically:

func SlidingLogAllow(ctx context.Context, rdb *redis.Client, key string, limit int, window time.Duration) (bool, error) {
    now := time.Now().UnixNano()
    minScore := now - int64(window)
    // Add current request timestamp
    rdb.ZAdd(ctx, key, &redis.Z{Score: float64(now), Member: now})
    // Trim old entries
    rdb.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", minScore))
    // Count recent requests
    cnt, err := rdb.ZCount(ctx, key, fmt.Sprintf("%d", minScore), fmt.Sprintf("%d", now)).Result()
    if err != nil {
        return false, err
    }
    return cnt <= int64(limit), nil
}

Sliding Window Counter Algorithm

The Sliding Window Counter combines aspects of fixed windows and sliding logs to reduce memory overhead. As documented in lines 97-104 of 04. Rate Limiter/Readme.md, this method maintains two counters (current and previous window) and calculates a weighted average to approximate the rolling count.

This algorithm offers better burst handling than fixed windows while consuming less memory than full logs, though it sacrifices absolute precision for efficiency.

In-Memory Implementation

The SlidingCounter struct tracks weighted requests across window boundaries:

type SlidingCounter struct {
    curWindow   int64
    prevWindow  int64
    curCount    int
    prevCount   int
    limit       int
    windowSize  time.Duration
    mu          sync.Mutex
}

func NewSlidingCounter(limit int, windowSize time.Duration) *SlidingCounter {
    now := time.Now().UnixNano()
    return &SlidingCounter{curWindow: now, limit: limit, windowSize: windowSize}
}

func (sc *SlidingCounter) Allow() bool {
    sc.mu.Lock()
    defer sc.mu.Unlock()
    now := time.Now().UnixNano()
    if now-sc.curWindow > int64(sc.windowSize) {
        // slide windows
        sc.prevWindow = sc.curWindow
        sc.prevCount = sc.curCount
        sc.curWindow = now
        sc.curCount = 0
    }
    // weighted count
    elapsed := now - sc.curWindow
    weight := float64(sc.windowSize-elapsed) / float64(sc.windowSize)
    approx := int(weight*float64(sc.prevCount)) + sc.curCount
    if approx >= sc.limit {
        return false
    }
    sc.curCount++
    return true
}

Summary

The liquidslr/system-design-notes repository provides practical implementations for five essential rate limiting strategies:

  • Token Bucket: Best for applications requiring burst tolerance with steady-state rate limiting; memory-efficient and straightforward to implement.
  • Leaking Bucket: Ideal for scenarios demanding constant outflow rates; simpler but less forgiving of traffic spikes.
  • Fixed Window Counter: Suitable for high-throughput systems where approximate limiting suffices and minimal latency is critical.
  • Sliding Window Log: Necessary when precise request accuracy is paramount and memory resources are abundant.
  • Sliding Window Counter: The balanced choice for distributed systems requiring reasonable accuracy with reduced memory footprint compared to full logs.

Frequently Asked Questions

Which rate limiting algorithm is best for handling traffic bursts?

Token Bucket is the optimal choice for handling traffic bursts because it accumulates tokens during idle periods, allowing temporary spikes up to the bucket capacity. Unlike the Leaking Bucket algorithm, which rigidly queues requests, the Token Bucket permits immediate processing of bursts as long as tokens remain available. This makes it ideal for APIs with unpredictable but generally low-average traffic patterns.

How does the Sliding Window Counter differ from Fixed Window Counter?

The Sliding Window Counter improves upon the Fixed Window Counter by maintaining two window counters and calculating a weighted average between them, preventing the "thundering herd" problem at window boundaries. While the Fixed Window Counter can allow double the intended traffic when requests straddle two windows, the Sliding Window Counter provides a smoother approximation of the rate limit across time boundaries.

What are the memory implications of these rate limiting algorithms?

Token Bucket and Leaking Bucket require only constant O(1) memory regardless of request volume, while Fixed Window and Sliding Window Counter need minimal state storage. In contrast, Sliding Window Log requires O(n) memory proportional to the number of requests within the window, as it must store every individual timestamp, making it unsuitable for high-throughput scenarios with large windows.

Can these algorithms work in distributed environments?

Yes, though single-node implementations like the TokenBucket and SlidingCounter structs require external coordination mechanisms for distributed deployments. The repository demonstrates Redis-backed implementations for Fixed Window (FixedWindowAllow) and Sliding Window Log (SlidingLogAllow) that leverage centralized storage for cross-instance rate limiting consistency.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →