Menu
Dev.to #systemdesign·August 5, 2026

Implementing Rate Limiting with the Token Bucket Algorithm

This article discusses the critical role of rate limiting in preventing system overload, illustrated by a real-world incident. It advocates for the token bucket algorithm as a robust solution, explaining its mechanism for smooth bursting and sustained traffic control. The piece also provides practical Go code examples and highlights common pitfalls in rate limiter implementation.

Read original on Dev.to #systemdesign

The Necessity of Rate Limiting

Rate limiting is a fundamental system design component essential for protecting services from abuse, misconfigurations, and traffic spikes. Without it, a sudden surge in requests can quickly overwhelm backend systems, leading to service degradation or complete outages. The article opens with a vivid anecdote of an API crashing due to an unregulated cron job, emphasizing the critical "why" behind implementing traffic control mechanisms.

Token Bucket Algorithm: The Balanced Approach

The token bucket algorithm is presented as a superior alternative to simpler fixed-window counters for rate limiting. It effectively addresses the "spike" problem where fixed windows allow clients to send a large burst of requests at the boundary of a time window. The token bucket model allows requests to consume tokens from a bucket that refills at a constant rate up to a maximum capacity. This design enables services to handle short bursts of traffic while ensuring that the long-term average request rate does not exceed a defined limit.

plaintext
+-------------------+
| Token Bucket      |
| (capacity = C)    |
+--------+----------+
^        |
tokens arrive at rate R (tokens/sec)
|        |
+--------v----------+
| Refill Timer      |
| (adds tokens)     |
+--------+----------+
|        |
request arrives -> try to take 1 token
v
+--------v----------+
| Consume?          |
| if token > 0:     |
| - decrement token |
| - allow request   |
| else:             |
| - reject/delay    |
+-------------------+
  1. Capacity (C): Defines the maximum number of tokens the bucket can hold, dictating the maximum burst size allowed.
  2. Rate (R): Specifies the rate at which tokens are added to the bucket, controlling the sustainable request rate over time.
  3. O(1) per-request cost: The algorithm's check for each request is constant time, making it highly efficient.

Key Implementation Considerations

💡

Common Pitfalls

Implementing a robust token bucket requires attention to details such as ensuring continuous token refill based on elapsed time (even during idle periods), using floating-point numbers for token counts to handle fractional rates accurately, and implementing proper concurrency control (like mutexes) to prevent race conditions in multi-threaded environments. Ignoring these can lead to incorrect rate limiting or even system crashes.

go
type TokenBucket struct {
  rate     float64 // tokens per second
  capacity float64 // max tokens
  tokens   float64
  lastSeen time.Time
  mu       sync.Mutex
}

func NewTokenBucket(rate float64, burst float64) *TokenBucket {
  return &TokenBucket{
    rate:     rate,
    capacity: burst,
    tokens:   burst, // start full so we can burst immediately
    lastSeen: time.Now(),
  }
}

// Allow checks if n tokens can be consumed.
// Returns true if the request is allowed, false otherwise.
func (b *TokenBucket) Allow(n int) bool {
  b.mu.Lock()
  defer b.mu.Unlock()

  now := time.Now()
  // refill based on elapsed time
  elapsed := now.Sub(b.lastSeen).Seconds()
  b.tokens += elapsed * b.rate
  if b.tokens > b.capacity {
    b.tokens = b.capacity
  }
  b.lastSeen = now

  if b.tokens >= float64(n) {
    b.tokens -= float64(n)
    return true
  }
  return false
}
rate limitingtoken buckettraffic controlconcurrencygoAPIsystem protectionperformance

Comments

Loading comments...