How would you implement rate limiting using channels?
Question 214MediumGo 1.22 to 1.25
// Token bucket: burst of 5, refill 10/s
func NewLimiter(ctx context.Context, rate time.Duration, burst int) <-chan struct{} {
tokens := make(chan struct{}, burst)
for range burst {
tokens <- struct{}{}
}
go func() {
t := time.NewTicker(rate)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
select {
case tokens <- struct{}{}: // refill
default: // bucket full, drop token
}
}
}
}()
return tokens
}
lim := NewLimiter(ctx, 100*time.Millisecond, 5)
for _, req := range requests {
<-lim // wait for a token
go handle(req)
}
The buffered channel is the bucket, the ticker refills it, and a non-blocking send drops overflow tokens so the burst stays capped. Interviewers expect you to mention that golang.org/x/time/rate (rate.NewLimiter(r, b) with Wait(ctx)) is the production choice. It needs no goroutine, handles fractional rates, and supports reservations, whereas the ticker version costs a goroutine and wakes up even when idle.
More on Channels & select
- Q212Implement a generic "first response wins" (hedged request / race) function.
- Q213What does this print? (request/response over a channel of channels)
- Q215Can you compare channels with
==, use them as map keys, and send channels over channels? - Q216How do you gracefully shut down a service that uses channels and goroutines?
- Q217What does this print? (
breakinsideselectinsidefor) - Q218What happens to goroutines that are already blocked on a channel when it is closed?