Design a per-client rate limiter for an HTTP API. Implement a token bucket.
Question 468HardGo 1.22 to 1.25
Token bucket: capacity b tokens, refilled at r per second; each request consumes one; allows bursts up to b. Compute tokens lazily from elapsed time — no background goroutine per bucket. In production, use golang.org/x/time/rate (rate.NewLimiter(r, b), Allow/Wait(ctx)).
type bucket struct {
tokens float64
last time.Time
}
type Limiter struct {
mu sync.Mutex
rate float64 // tokens per second
burst float64
buckets map[string]*bucket
}
func NewLimiter(ctx context.Context, rate float64, burst int) *Limiter {
l := &Limiter{rate: rate, burst: float64(burst), buckets: make(map[string]*bucket)}
go l.evict(ctx, 5*time.Minute) // prevent unbounded map growth
return l
}
func (l *Limiter) Allow(key string) bool {
now := time.Now()
l.mu.Lock()
defer l.mu.Unlock()
b, ok := l.buckets[key]
if !ok {
b = &bucket{tokens: l.burst, last: now}
l.buckets[key] = b
}
b.tokens = min(l.burst, b.tokens+now.Sub(b.last).Seconds()*l.rate) // Go 1.21 min
b.last = now
if b.tokens < 1 {
return false
}
b.tokens--
return true
}
func (l *Limiter) evict(ctx context.Context, idle time.Duration) {
t := time.NewTicker(idle)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case now := <-t.C:
l.mu.Lock()
for k, b := range l.buckets {
if now.Sub(b.last) > idle {
delete(l.buckets, k)
}
}
l.mu.Unlock()
}
}
}
func (l *Limiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
if !l.Allow(ip) {
w.Header().Set("Retry-After", "1")
http.Error(w, "rate limited", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
Discussion points: a single mutex becomes contention — shard the map by key hash; behind a proxy, key by authenticated user or a trusted X-Forwarded-For; across multiple replicas, move state to Redis (atomic Lua script / GCRA) or accept per-instance limits.
More on Standard Library, HTTP & Systems Design in Go
- Q466Explain the laws of reflection and settability. What does this print?
- Q467Use reflection to build a simple struct-tag validator (`validate:"required,max=10"`).
- Q469Design a concurrency-safe, generic LRU cache with TTL.
- Q470What is a cache stampede and how does singleflight solve it?
- Q471Design an in-process pub/sub broker. How do you handle slow subscribers?
- Q472Fan out N HTTP calls with bounded concurrency, fail fast on first error, and collect results in order.