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
rust
#[derive(Debug, Clone, PartialEq)] pub enum Token { Number(f64), Plus,
How a tokenizer turns text into tokens
lexing
enums
iterators
Intermediate
8 steps
ruby
module UniqueJob extend ActiveSupport::Concern class_methods do
Deduplicating Active Job enqueues in Rails
concurrency
idempotency
caching
Advanced
9 steps
java
public class TimedFetchService { private final ExecutorService executor = Executors.newFixedThreadPool(8); private final HttpClient httpClient = HttpClient.newHttpClient();
Enforcing HTTP timeouts with a Future
concurrency
timeouts
thread-pool
Intermediate
8 steps
go
package middleware import ( "net/http"
Role-based access control middleware in Gin
middleware
authorization
closures
Intermediate
7 steps
go
func UserDashboardCache(rdb *redis.Client, ttl time.Duration) gin.HandlerFunc { return func(c *gin.Context) { claims, ok := c.Get("claims") if !ok {
Per-user response caching in Gin with Redis
middleware
caching
redis
Advanced
9 steps
javascript
import { unstable_cache, revalidateTag } from 'next/cache' import { db } from '@/lib/db' export const getDashboardStats = unstable_cache(
Caching dashboard stats in Next.js
caching
cache-invalidation
tag-based-revalidation
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/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.