go
75 lines · 8 steps
How a circuit breaker works in Go
A CircuitBreaker tracks failures and stops calling a failing dependency until a cooldown lets it retry.
Explained by
highlit
1package breaker
2
3import (
4 "errors"
5 "sync"
6 "time"
7)
8
9var ErrOpenCircuit = errors.New("circuit breaker is open")
10
11type State int
12
13const (
14 Closed State = iota
15 Open
16 HalfOpen
17)
18
19type CircuitBreaker struct {
20 mu sync.Mutex
21 state State
22 failures int
23 failureThreshold int
24 cooldown time.Duration
25 openedAt time.Time
26}
27
28func New(threshold int, cooldown time.Duration) *CircuitBreaker {
29 return &CircuitBreaker{
30 state: Closed,
31 failureThreshold: threshold,
32 cooldown: cooldown,
33 }
34}
35
36func (cb *CircuitBreaker) Execute(fn func() error) error {
37 if err := cb.beforeCall(); err != nil {
38 return err
39 }
40
41 err := fn()
42
43 cb.afterCall(err)
44 return err
45}
46
47func (cb *CircuitBreaker) beforeCall() error {
48 cb.mu.Lock()
49 defer cb.mu.Unlock()
50
51 if cb.state == Open {
52 if time.Since(cb.openedAt) < cb.cooldown {
53 return ErrOpenCircuit
54 }
55 cb.state = HalfOpen
56 }
57 return nil
58}
59
60func (cb *CircuitBreaker) afterCall(err error) {
61 cb.mu.Lock()
62 defer cb.mu.Unlock()
63
64 if err != nil {
65 cb.failures++
66 if cb.state == HalfOpen || cb.failures >= cb.failureThreshold {
67 cb.state = Open
68 cb.openedAt = time.Now()
69 }
70 return
71 }
72
73 cb.failures = 0
74 cb.state = Closed
75}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A circuit breaker trades a few fast failures for protecting a struggling downstream service from more load.
- 2Modeling behavior as an explicit state machine (Closed, Open, HalfOpen) keeps the transition logic clear and testable.
- 3Guarding shared mutable state with a mutex makes the breaker safe to call from many goroutines at once.
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
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
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 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/how-a-circuit-breaker-works-in-go-explained-go-ae4b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.