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

Walkthrough

Space play step click any line
Three takeaways
  1. 1A token bucket smooths bursts by capping tokens while replenishing at a fixed rate.
  2. 2Guarding shared counters with a mutex keeps concurrent producers and consumers correct.
  3. 3Pairing a done channel with a ticker gives goroutines a clean, leak-free shutdown path.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A token bucket rate limiter in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code