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
typescript
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpHandler,
Refreshing tokens with an Angular HttpInterceptor
http-interceptor
token-refresh
rxjs
Advanced
8 steps
go
package handlers type BookURI struct { Collection string `uri:"collection" binding:"required,alphanum"`
Validating URI params in a Gin handler
input validation
struct binding
error handling
Intermediate
7 steps
typescript
type Fetcher<T> = (key: string) => Promise<T>; export class RequestDeduplicator<T> { private inFlight = new Map<string, Promise<T>>();
Deduplicating in-flight requests in TypeScript
deduplication
promises
caching
Intermediate
7 steps
go
package cache import ( "context"
Cache-aside reads with singleflight in Go
caching
singleflight
concurrency
Advanced
8 steps
rust
use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Instant; pub struct TimedGuard<'a, T> {
A self-timing mutex guard in Rust
raii
mutex
deref
Advanced
7 steps
go
package httpx import ( "net/http"
A retrying HTTP transport in Go
http
middleware
retry
Intermediate
8 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.