go
69 lines · 7 steps
A token bucket rate limiter in Go
A background goroutine refills tokens on a ticker while callers spend them under a mutex to throttle throughput.
Explained by
highlit
1package ratelimit
2
3import (
4 "context"
5 "sync"
6 "time"
7)
8
9type TokenBucket struct {
10 mu sync.Mutex
11 tokens int
12 capacity int
13 ticker *time.Ticker
14 done chan struct{}
15}
16
17func NewTokenBucket(capacity int, refill time.Duration) *TokenBucket {
18 b := &TokenBucket{
19 tokens: capacity,
20 capacity: capacity,
21 ticker: time.NewTicker(refill),
22 done: make(chan struct{}),
23 }
24 go b.refillLoop()
25 return b
26}
27
28func (b *TokenBucket) refillLoop() {
29 for {
30 select {
31 case <-b.ticker.C:
32 b.mu.Lock()
33 if b.tokens < b.capacity {
34 b.tokens++
35 }
36 b.mu.Unlock()
37 case <-b.done:
38 return
39 }
40 }
41}
42
43func (b *TokenBucket) Allow() bool {
44 b.mu.Lock()
45 defer b.mu.Unlock()
46 if b.tokens > 0 {
47 b.tokens--
48 return true
49 }
50 return false
51}
52
53func (b *TokenBucket) Wait(ctx context.Context) error {
54 for {
55 if b.Allow() {
56 return nil
57 }
58 select {
59 case <-ctx.Done():
60 return ctx.Err()
61 case <-b.ticker.C:
62 }
63 }
64}
65
66func (b *TokenBucket) Stop() {
67 b.ticker.Stop()
68 close(b.done)
69}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A token bucket smooths bursts by capping tokens while replenishing at a fixed rate.
- 2Guarding shared counters with a mutex keeps concurrent producers and consumers correct.
- 3Pairing a done channel with a ticker gives goroutines a clean, leak-free shutdown path.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
rust
use std::collections::VecDeque; use std::sync::{Arc, Condvar, Mutex}; use std::time::{Duration, Instant};
Building a counting semaphore in Rust
concurrency
synchronization
condition-variable
Advanced
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-token-bucket-rate-limiter-in-go-explained-go-ec38/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.