In-memory rate limiter middleware in Go
rate limiting and middleware design.
use a token-bucket limiter (golang.org/x/time/rate), guard a per-client map with sync.Mutex, wrap http.Handler so requests over the limit get 429.
WHAT THIS TESTS Whether you can implement a concurrent, per-client rate limiter and integrate it cleanly as HTTP middleware in Go.
A GOOD ANSWER COVERS Use a token-bucket algorithm, conveniently provided by golang.org/x/time/rate, whose Limiter is created with rate.NewLimiter(rate.Limit(r), burst) and exposes Allow for a non-blocking check. For per-client limiting, maintain a map from a client key, typically the remote IP, to its own Limiter, since one global limiter would throttle everyone together. Because handlers run concurrently, guard that map with a sync.Mutex, or use sync.Map; on lookup, create and store a new limiter the first time you see a client. Write the middleware as a function taking an http.Handler and returning an http.Handler via http.HandlerFunc: it extracts the client key, fetches that client's limiter, calls Allow, and if it returns false responds with http.StatusTooManyRequests, 429; otherwise it calls next.ServeHTTP. Chaining works because the wrapper itself satisfies http.Handler.
COMMON WRONG ANSWERS Using one shared limiter for all clients, which is not per-client fairness. Accessing the map without synchronization, causing data races. Leaking memory by never evicting stale per-client limiters. Blocking on Wait inside a handler when you meant the non-blocking Allow. Using a naive counter reset on a ticker, which permits bursts at boundaries.
LIKELY FOLLOW-UPS How do you evict idle clients to bound memory, perhaps with last-seen timestamps and a sweeper goroutine? Why token bucket over fixed-window counting? How would you set Retry-After on a 429? How does this differ from a distributed limiter backed by Redis?
ONE CONCRETE EXAMPLE A middleware keyed by IP: limiters is a map guarded by mu; getLimiter(ip) locks, returns or creates rate.NewLimiter(1, 5); the handler does if !getLimiter(ip).Allow() { http.Error(w, "rate limited", http.StatusTooManyRequests); return }; next.ServeHTTP(w, r). This gives each IP roughly one request per second with a burst of five, returning 429 when exceeded.
Read the original → pkg.go.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.