Skip to content
intermediatePhase 48 · Distributed Systems

Rate Limiting

Protect APIs with token bucket, sliding window, or leaky bucket algorithms.

1h
0 problems
Topic Progress0%

Token Bucket

Token Bucket Algorithm

Token Bucket controls rate by allowing bursts while maintaining average rate.

How it Works

Bucket with capacity C tokens
Refill rate R tokens per second

1. Request arrives
2. If bucket has tokens:
   - Remove one token
   - Allow request
3. If bucket empty:
   - Reject request

Example: C=10, R=2/sec
- Burst: 10 requests instantly
- Sustained: 2 requests/sec

Implementation

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity
        self.refill_rate = refill_rate  # tokens per second
        self.tokens = capacity
        self.last_refill = time.time()
        self.lock = threading.Lock()
    
    def allow(self):
        with self.lock:
            self._refill()
            if self.tokens >= 1:
                self.tokens -= 1
                return True
            return False
    
    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        new_tokens = elapsed * self.refill_rate
        self.tokens = min(self.capacity, self.tokens + new_tokens)
        self.last_refill = now

# Usage
limiter = TokenBucket(capacity=100, refill_rate=10)  # 100 burst, 10/sec sustained

if limiter.allow():
    process_request()
else:
    return 429  # Too Many Requests

Characteristics

Aspect Value
Burst handling Excellent (allows bursts up to capacity)
Memory O(1)
Accuracy Good
Complexity Low

Sliding Window

Sliding Window Rate Limiting

Sliding Window tracks requests in a time window that slides forward.

Variants

1. Sliding Window Log:
   Store timestamp of each request
   Count requests in window
   Accurate but memory-heavy

2. Sliding Window Counter:
   Combine current and previous window counts
   Weighted average
   Approximate but efficient

Implementation

class SlidingWindowLog:
    def __init__(self, window_seconds, max_requests):
        self.window = window_seconds
        self.max_requests = max_requests
        self.timestamps = []
        self.lock = threading.Lock()
    
    def allow(self):
        with self.lock:
            now = time.time()
            cutoff = now - self.window
            
            # Remove old timestamps
            self.timestamps = [t for t in self.timestamps if t > cutoff]
            
            if len(self.timestamps) < self.max_requests:
                self.timestamps.append(now)
                return True
            return False

class SlidingWindowCounter:
    def __init__(self, window_seconds, max_requests):
        self.window = window_seconds
        self.max_requests = max_requests
        self.prev_count = 0
        self.curr_count = 0
        self.window_start = time.time()
    
    def allow(self):
        now = time.time()
        self._rotate_window(now)
        
        # Weighted count
        elapsed = now - self.window_start
        weight = 1 - (elapsed / self.window)
        estimated = self.prev_count * weight + self.curr_count
        
        if estimated < self.max_requests:
            self.curr_count += 1
            return True
        return False
    
    def _rotate_window(self, now):
        if now - self.window_start >= self.window:
            self.prev_count = self.curr_count
            self.curr_count = 0
            self.window_start = now

Comparison

Method Accuracy Memory Performance
Log Exact High Medium
Counter Approximate Low High

Leaky Bucket

Leaky Bucket Algorithm

Leaky Bucket processes requests at a fixed rate, smoothing bursts.

How it Works

Bucket with capacity C
Leak rate R per second

1. Request arrives
2. If bucket not full:
   - Add to bucket
   - Allow request
3. If bucket full:
   - Reject request

Requests processed at constant rate R
Bursts are smoothed out

Implementation

class LeakyBucket:
    def __init__(self, capacity, leak_rate):
        self.capacity = capacity
        self.leak_rate = leak_rate  # requests per second
        self.water = 0  # current water level
        self.last_leak = time.time()
        self.lock = threading.Lock()
    
    def allow(self):
        with self.lock:
            self._leak()
            if self.water < self.capacity:
                self.water += 1
                return True
            return False
    
    def _leak(self):
        now = time.time()
        elapsed = now - self.last_leak
        leaked = elapsed * self.leak_rate
        self.water = max(0, self.water - leaked)
        self.last_leak = now

Token Bucket vs Leaky Bucket

Aspect Token Bucket Leaky Bucket
Burst handling Allows bursts Smooths bursts
Output rate Variable Fixed
Use case API rate limiting Traffic shaping

Distributed Rate Limiting

Distributed Rate Limiting

Challenges

Problem:
- Multiple nodes
- Need global rate limit
- Network latency
- Race conditions

Solutions

1. Centralized:
   Single rate limit service
   Simple but single point of failure

2. Distributed with Redis:
   Use Redis for coordination
   Atomic operations

3. Local + Synchronization:
   Local rate limiting
   Periodic sync between nodes

4. Consistent Hashing:
   Route by client ID
   Each node limits its shard

Redis Implementation

class DistributedRateLimiter:
    def __init__(self, redis_client, rate, window):
        self.redis = redis_client
        self.rate = rate
        self.window = window
    
    def allow(self, key):
        """Sliding window with Redis"""
        now = time.time()
        window_start = now - self.window
        
        pipe = self.redis.pipeline()
        pipe.zremrangebyscore(key, 0, window_start)  # Remove old
        pipe.zadd(key, {str(now): now})  # Add current
        pipe.zcard(key)  # Count
        pipe.expire(key, self.window)  # Set TTL
        
        results = pipe.execute()
        request_count = results[2]
        
        return request_count <= self.rate

# Usage
limiter = DistributedRateLimiter(redis, rate=100, window=60)

if limiter.allow(f"user:{user_id}"):
    process_request()
else:
    return 429

Strategies

Strategy Consistency Performance Complexity
Centralized Strong Low Low
Redis Strong High Medium
Local + Sync Eventual Highest High
Consistent Hash Per-shard High Medium

Best Practices

  1. Use Redis for distributed rate limiting
  2. Implement local caching for hot keys
  3. Handle Redis failures gracefully
  4. Monitor rate limit metrics
  5. Provide clear error responses (429 + Retry-After)

Practice Problems

0/3solved
Design Rate Limiting System

Design a scalable Rate Limiting system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Rate Limiting Scaling

How would you scale Rate Limiting to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Rate Limiting Failure Modes

Analyze potential failure modes for Rate Limiting and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What is Token Bucket algorithm?

Question 1 options

2. How does Leaky Bucket differ from Token Bucket?

Question 2 options

3. What is Sliding Window Counter?

Question 3 options

4. Why use Redis for distributed rate limiting?

Question 4 options

5. What HTTP status code for rate limiting?

Question 5 options

Flashcards

Question

Token Bucket vs Leaky Bucket?

Answer

Token Bucket: allows bursts, variable rate. Leaky Bucket: smooths bursts, fixed rate.

Question

What is Sliding Window?

Answer

Tracks requests in a time window that slides forward; Counter variant uses weighted average for efficiency

Question

How to do distributed rate limiting?

Answer

Use Redis for atomic operations, or consistent hashing to distribute by client ID

Question

HTTP status for rate limiting?

Answer

429 Too Many Requests with Retry-After header

Question

Token Bucket characteristics?

Answer

O(1) memory, allows bursts up to capacity, good accuracy, low complexity

Revision Notes

Key Takeaways

  • 1.Token Bucket allows bursts while maintaining average rate
  • 2.Leaky Bucket smooths bursts at fixed rate
  • 3.Sliding Window Counter provides efficient approximation
  • 4.Redis enables distributed rate limiting with atomic ops
  • 5.Return 429 with Retry-After header

Interview Tips

  • Compare Token Bucket vs Leaky Bucket
  • Explain Sliding Window Counter approximation
  • Discuss distributed rate limiting challenges
  • Mention 429 + Retry-After best practice

Cheat Sheet

Cheat Sheet: Rate Limiting

Algorithms

  1. Token Bucket: Allows bursts
  2. Leaky Bucket: Smooths bursts
  3. Sliding Window: Tracks in window
  4. Fixed Window: Simple but boundary issues

Token Bucket

  • Capacity: burst size
  • Refill rate: sustained rate
  • O(1) memory

Sliding Window

  • Log: Exact, high memory
  • Counter: Approximate, low memory

Distributed

  • Redis: Atomic operations
  • Consistent hash: Shard by client
  • HTTP 429 + Retry-After