{"slug": "circuit-breaker-pattern-in-go-stop-cascading-failures", "title": "Circuit Breaker Pattern in Go: Stop Cascading Failures", "summary": "A circuit breaker pattern in Go stops cascading failures by failing fast when a dependency crosses a failure threshold, protecting the system from slow or unhealthy services. The pattern uses three states—closed, open, and half-open—and is distinct from timeouts and retries, which solve different problems. Proper placement and configuration are critical to avoid introducing new failure modes.", "body_md": "# Circuit Breaker Pattern in Go: Stop Cascading Failures\n\nStop cascading failures in Go microservices.\n\nA circuit breaker stops your Go service from hammering a failing dependency, preventing cascading failures that consume goroutines, sockets, and memory until the entire system collapses.\n\nThe hard part is not the state machine. It is deciding where the breaker belongs, what counts as failure, how it interacts with timeouts and retries, and what your service should do when the circuit is open.\n\nIn Go, the circuit breaker pattern is especially useful around outbound calls: HTTP APIs, payment gateways, search services, email providers, LLM gateways, internal microservices, and other dependencies that can become slow, overloaded, or partially unavailable. Used well, a circuit breaker reduces cascading failures. Used badly, it becomes another obscure failure mode.\n\n## What Problem Does a Circuit Breaker Solve?\n\nDistributed systems rarely fail cleanly.\n\nA dependency might not be fully down. It might be:\n\n- returning 500 errors\n- returning 429 rate limit responses\n- accepting TCP connections but never replying\n- responding in 30 seconds instead of 300 milliseconds\n- failing only for some requests\n- overloaded because every client is retrying at once\n\nThe worst case is often not a hard failure. It is a slow dependency.\n\nSlow calls consume goroutines, sockets, database connections, memory, and worker capacity. If your service keeps waiting on a dependency that is already unhealthy, your service can become unhealthy too.\n\nA circuit breaker prevents that by failing fast after the dependency crosses a failure threshold.\n\nInstead of doing this forever:\n\n``` php\nrequest -> call dependency -> wait -> timeout -> retry -> wait -> fail\n```\n\nthe service eventually does this:\n\n``` php\nrequest -> circuit open -> return fallback or error immediately\n```\n\nThat fast failure is not always pleasant, but it is predictable. Predictable failure is easier to operate than a slow collapse.\n\n## The Three Circuit Breaker States\n\nMost circuit breakers use three states.\n\n### Closed\n\nThe circuit is closed during normal operation.\n\nRequests are allowed through. The breaker records successes and failures. If the number or ratio of failures crosses a threshold, the breaker opens.\n\nClosed does not mean “safe forever.” It means “traffic is currently allowed.”\n\n### Open\n\nThe circuit is open when the dependency is considered unhealthy.\n\nRequests are rejected immediately. The service should return a fallback, cached response, degraded response, or a clear upstream error.\n\nOpen does not fix the dependency. It gives the dependency time to recover and protects the caller from wasting resources.\n\n### Half-Open\n\nAfter a cool-down period, the breaker enters a half-open state.\n\nOnly a limited number of trial requests are allowed through. If they succeed, the breaker closes. If they fail, the breaker opens again.\n\nHalf-open is important because it avoids two bad extremes:\n\n- never trying the dependency again\n- sending full traffic back too quickly\n\nThe state transitions look like this:\n\n## Circuit Breaker vs Timeout vs Retry\n\nA common mistake is treating circuit breakers, retries, and timeouts as interchangeable. They are related, but they solve different problems.\n\n### Timeout\n\nA timeout limits how long one operation can run.\n\nIn Go, this usually means passing a `context.Context`\n\nwith a deadline or timeout into the outbound call.\n\nA timeout answers this question:\n\n```\nHow long am I willing to wait for this one call?\n```\n\n### Retry\n\nA retry repeats an operation when the failure might be temporary.\n\nRetries are useful for short network glitches, temporary 503 responses, connection resets, and other transient failures.\n\nA retry answers this question:\n\n```\nShould I try this call again?\n```\n\n### Circuit Breaker\n\nA circuit breaker stops calls when the dependency is probably unhealthy.\n\nIt answers this question:\n\n```\nShould I call this dependency at all right now?\n```\n\n### Rate Limiter\n\nA rate limiter controls how much traffic is allowed over time.\n\nIt answers this question:\n\n```\nHow much traffic should this caller send?\n```\n\n### Bulkhead\n\nA bulkhead isolates resources so one dependency cannot consume everything.\n\nIt answers this question:\n\n```\nHow much of my service can this dependency damage?\n```\n\nThese patterns are strongest when used together. A circuit breaker without timeouts is weak. Retries without jitter can create retry storms. A fallback without metrics can hide an outage.\n\n## When to Use a Circuit Breaker in Go\n\nUse a circuit breaker when your service calls a dependency that can fail independently from your service.\n\nGood candidates include:\n\n- external HTTP APIs\n- payment processors\n- email and SMS providers\n- search services\n- recommendation services\n- LLM inference gateways\n- internal microservice endpoints\n- third-party SaaS APIs\n- slow or overloaded read-side services\n\nCircuit breakers are especially useful when the caller can degrade gracefully.\n\nFor example:\n\n- return cached product data\n- skip a recommendation block\n- mark a payment provider as temporarily unavailable\n- queue work for later\n- return a partial response\n- fail fast with a clear temporary error\n\nThe important question is not “can this call fail?” Everything can fail. The better question is:\n\n```\nIf this dependency is failing, should we continue sending full traffic to it?\n```\n\nIf the answer is no, a circuit breaker may help.\n\n## When Not to Use a Circuit Breaker\n\nDo not add a circuit breaker to every function just because the pattern sounds responsible.\n\nA circuit breaker is usually not useful for:\n\n- local in-process function calls\n- simple CRUD inside a monolith\n- validation logic\n- deterministic business rules\n- CPU-only local operations\n- code paths where no useful fallback exists\n- write operations that are not idempotent\n- dependencies already protected by a stronger workflow layer\n\nA circuit breaker also does not replace basic hygiene:\n\n- set timeouts\n- propagate context\n- use connection pools correctly\n- handle errors explicitly\n- make retries safe\n- observe failure rates\n\nA bad circuit breaker can make a system harder to reason about. It can hide the real problem, reject traffic too aggressively, or create confusing behavior during recovery.\n\nThe slightly opinionated rule is simple:\n\n```\nAdd circuit breakers at dependency boundaries, not everywhere.\n```\n\n## Choosing a Go Circuit Breaker Library\n\nYou can implement a basic circuit breaker yourself, but most production Go services should use a library.\n\nThe most common simple choice is `sony/gobreaker`\n\n.\n\nIt gives you:\n\n- closed, open, and half-open states\n- configurable failure thresholds\n- configurable open-state timeout\n- state change callbacks\n- request counters\n- generic support in v2\n- a small API surface\n\nFor larger resilience pipelines, you may also look at libraries that compose multiple policies, such as retry, timeout, fallback, rate limiting, bulkhead isolation, and circuit breaking. That can be useful when you want a single resilience layer around an operation.\n\nFor many Go services, though, `gobreaker`\n\nis enough.\n\n## Go Circuit Breaker Packages Compared\n\nGo does not include a built-in circuit breaker in the standard library. In practice, you usually choose between a small circuit breaker library, a larger resilience framework, or an older Hystrix-style package.\n\nFor most new Go services, the decision is simple:\n\n- use\n`sony/gobreaker`\n\nif you want a small, focused circuit breaker - use\n`failsafe-go`\n\nif you want circuit breakers composed with retries, timeouts, fallbacks, bulkheads, rate limits, and other resilience policies - avoid starting new projects on\n`hystrix-go`\n\nunless you already have legacy code using it\n\n| Package | Best for | Strengths | Tradeoffs |\n|---|---|---|---|\n`sony/gobreaker/v2` |\nSimple circuit breakers around HTTP/RPC clients | Small API, generic v2 support, clear state model, easy to wrap dependency clients | Only solves circuit breaking; retries, timeouts, and fallbacks must be composed separately |\n`failsafe-go` |\nFull resilience policy composition | Retry, fallback, circuit breaker, timeout, bulkhead, rate limiter, cache, hedge, adaptive limiter, and adaptive throttler policies | More concepts to learn; heavier than needed if you only want a basic breaker |\n`afex/hystrix-go` |\nLegacy Hystrix-style systems | Familiar Hystrix concepts, command-style execution, historical usage | Older design; not the best default for new Go services |\n`go-kit/kit/circuitbreaker` |\nGo kit endpoint-based services | Fits Go kit middleware style and endpoint architecture | Mostly useful if your service already uses Go kit |\n`cep21/circuit` |\nHystrix-like circuit breaker behavior | More featureful Hystrix-style approach | Less common as the simple default; may be more than needed for small services |\n\nMy default recommendation is boring on purpose: start with `sony/gobreaker/v2`\n\nwhen you only need a circuit breaker. Reach for `failsafe-go`\n\nwhen you want to express a complete resilience policy in one place.\n\nThat split keeps the architecture clean. A small service client does not need a full resilience framework just to stop calling a failing dependency. But a gateway, aggregator, API client SDK, or high-traffic integration layer may benefit from composed policies.\n\n## Installing gobreaker\n\nUse the v2 package for new code:\n\n```\ngo get github.com/sony/gobreaker/v2\n```\n\nThen import it:\n\n```\nimport \"github.com/sony/gobreaker/v2\"\n```\n\n## A Basic Circuit Breaker in Go\n\nHere is a small example around an HTTP call.\n\n```\npackage main\n\nimport (\n    \"context\"\n    \"errors\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/sony/gobreaker/v2\"\n)\n\nvar ErrTemporaryUnavailable = errors.New(\"dependency temporarily unavailable\")\n\ntype UserClient struct {\n    baseURL string\n    http    *http.Client\n    cb      *gobreaker.CircuitBreaker[[]byte]\n}\n\nfunc NewUserClient(baseURL string) *UserClient {\n    settings := gobreaker.Settings{\n        Name:        \"user-service\",\n        MaxRequests: 3,\n        Interval:    30 * time.Second,\n        Timeout:     10 * time.Second,\n        ReadyToTrip: func(counts gobreaker.Counts) bool {\n            return counts.ConsecutiveFailures >= 5\n        },\n        OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {\n            fmt.Printf(\"circuit breaker %s changed from %s to %s\\n\", name, from, to)\n        },\n    }\n\n    return &UserClient{\n        baseURL: baseURL,\n        http: &http.Client{\n            Timeout: 3 * time.Second,\n        },\n        cb: gobreaker.NewCircuitBreaker[[]byte](settings),\n    }\n}\n\nfunc (c *UserClient) GetUser(ctx context.Context, userID string) ([]byte, error) {\n    result, err := c.cb.Execute(func() ([]byte, error) {\n        req, err := http.NewRequestWithContext(\n            ctx,\n            http.MethodGet,\n            c.baseURL+\"/users/\"+userID,\n            nil,\n        )\n        if err != nil {\n            return nil, err\n        }\n\n        resp, err := c.http.Do(req)\n        if err != nil {\n            return nil, err\n        }\n        defer resp.Body.Close()\n\n        if resp.StatusCode >= 500 {\n            return nil, fmt.Errorf(\"user service returned %d\", resp.StatusCode)\n        }\n\n        if resp.StatusCode == http.StatusNotFound {\n            return nil, fmt.Errorf(\"user not found\")\n        }\n\n        if resp.StatusCode >= 400 {\n            return nil, fmt.Errorf(\"user service client error: %d\", resp.StatusCode)\n        }\n\n        return io.ReadAll(resp.Body)\n    })\n\n    if errors.Is(err, gobreaker.ErrOpenState) {\n        return nil, ErrTemporaryUnavailable\n    }\n\n    if errors.Is(err, gobreaker.ErrTooManyRequests) {\n        return nil, ErrTemporaryUnavailable\n    }\n\n    return result, err\n}\n```\n\nThis is not a complete production client, but it shows the shape:\n\n- the breaker wraps the outbound call\n- the HTTP request receives a context\n- the HTTP client has a timeout\n- server-side failures count as breaker failures\n- open circuit errors are mapped into an application error\n\n## Configuring gobreaker Settings\n\nThe key settings are worth understanding.\n\n### Name\n\n`Name`\n\nidentifies the breaker.\n\nUse a stable, specific name:\n\n```\npayment-api\nsearch-service\nllm-gateway\nuser-service\n```\n\nAvoid vague names like:\n\n```\nhttp-client\nexternal-call\ndefault\n```\n\nYou will want this name in logs and metrics.\n\n### MaxRequests\n\n`MaxRequests`\n\ncontrols how many requests are allowed while the breaker is half-open.\n\nA small number is usually safer. The purpose of half-open is to test recovery, not to send full traffic immediately.\n\n### Interval\n\n`Interval`\n\ncontrols when internal counts are cleared while the breaker is closed.\n\nIf it is zero, counts are not cleared automatically. A non-zero interval gives the breaker a rolling-ish memory window, although it is not the same as a full sliding window implementation.\n\n### Timeout\n\n`Timeout`\n\ncontrols how long the breaker stays open before moving to half-open.\n\nIf the timeout is too short, your service will keep probing a dependency that has not recovered. If it is too long, recovery will be delayed.\n\nStart with something conservative, such as 10 to 30 seconds, then tune from production metrics.\n\n### ReadyToTrip\n\n`ReadyToTrip`\n\ndecides when the breaker should open.\n\nA simple rule is consecutive failures:\n\n```\nReadyToTrip: func(counts gobreaker.Counts) bool {\n    return counts.ConsecutiveFailures >= 5\n}\n```\n\nThat is easy to reason about, but it may not be right for high-volume services.\n\nAnother option is failure ratio after a minimum number of requests:\n\n```\nReadyToTrip: func(counts gobreaker.Counts) bool {\n    total := counts.Requests\n    failures := counts.TotalFailures\n\n    if total < 20 {\n        return false\n    }\n\n    return float64(failures)/float64(total) >= 0.5\n}\n```\n\nThis avoids opening the circuit after a tiny sample size.\n\n### OnStateChange\n\n`OnStateChange`\n\nis where you should emit logs or metrics.\n\nAt minimum, record:\n\n- breaker name\n- old state\n- new state\n- timestamp\n\nFor production systems, expose breaker state as a metric. Logs are useful for debugging, but metrics are better for alerting and dashboards.\n\n### IsSuccessful\n\n`IsSuccessful`\n\nlets you decide which errors count as failures.\n\nThis is important.\n\nNot every error should open the breaker. For example, a `404 Not Found`\n\nfrom a user service may be a valid business result. A `400 Bad Request`\n\nmight be the caller’s fault, not the dependency’s fault.\n\nA `503 Service Unavailable`\n\n, timeout, connection reset, or `429 Too Many Requests`\n\nmay be a real dependency health signal.\n\nBe careful here. Counting the wrong errors is one of the easiest ways to build a noisy circuit breaker.\n\n## What Should Count as Failure?\n\nThis is where engineering judgement matters.\n\nUsually count these as failures:\n\n- network timeouts\n- connection refused\n- connection reset\n- HTTP 500\n- HTTP 502\n- HTTP 503\n- HTTP 504\n- repeated 429 responses\n- malformed responses from the dependency\n- context deadline exceeded during the outbound call\n\nUsually do not count these as dependency failures:\n\n- validation errors\n- local serialization errors\n- expected 404 responses\n- caller-side authorization failures\n- business rule rejections\n- user input errors\n\nThe breaker should represent dependency health, not general application failure.\n\n## Circuit Breakers and context.Context\n\nIn Go, circuit breakers should not replace `context.Context`\n\n.\n\nA circuit breaker decides whether to attempt a call. A context controls how long that call may run and whether it should stop when the caller is gone.\n\nA good outbound call should usually have both:\n\n```\nctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)\ndefer cancel()\n\ndata, err := client.GetUser(ctx, userID)\n```\n\nThe context should flow through the call chain:\n\n``` php\nincoming request context\n-> service method\n-> client method\n-> HTTP request\n-> dependency\n```\n\nAvoid creating detached background contexts inside request-scoped code. If the user request is canceled, the downstream work should usually stop too.\n\nThe calm rule is:\n\n```\nThe breaker protects the system. The context protects the request.\n```\n\nYou normally need both.\n\n## Circuit Breakers and Retries\n\nRetries and circuit breakers can work well together, but the order matters.\n\nThe safest default is:\n\n```\ntimeout per attempt\nretry with backoff and jitter\ncircuit breaker around the dependency call\n```\n\nBut there is no universal answer. Think about what you want to count.\n\nIf each retry attempt passes through the breaker, one user request can contribute multiple failures. That may open the breaker faster, which can be good or bad.\n\nIf the breaker wraps the whole retry operation, the breaker sees one final success or failure per user request. That is calmer, but it may hide the number of failed attempts.\n\nFor many application services, this shape is reasonable:\n\n``` php\nuser request\n-> circuit breaker\n   -> retry policy\n      -> one HTTP attempt with timeout\n```\n\nThat means the breaker tracks whether the dependency operation ultimately worked for the caller.\n\nFor lower-level clients, this shape can also make sense:\n\n``` php\nuser request\n-> retry policy\n   -> circuit breaker\n      -> one HTTP attempt with timeout\n```\n\nThat means the breaker protects each attempt.\n\nThe more important rule is this:\n\n```\nDo not retry blindly.\n```\n\nUse:\n\n- a small maximum retry count\n- exponential backoff\n- jitter\n- per-attempt timeouts\n- an overall request deadline\n- idempotency for writes\n- metrics for retry attempts\n\nWithout those, retries can turn a small outage into a larger one. For a deeper treatment of retry safety, see [Idempotency in Distributed Systems That Actually Works](https://www.glukhov.org/app-architecture/integration-patterns/idempotency-in-distributed-systems/).\n\n## Circuit Breakers and Idempotency\n\nCircuit breakers often appear next to retries, and retries raise the question of idempotency.\n\nFor read operations, retrying is usually safe.\n\nFor write operations, retrying can be dangerous.\n\nConsider this payment call:\n\n```\nPOST /charge\n```\n\nIf the request times out, did the payment fail? Maybe. Did it succeed but the response was lost? Also maybe.\n\nIf you retry without an idempotency key, you might charge twice.\n\nFor write operations, use one or more of these:\n\n- idempotency keys\n- request IDs\n- operation IDs\n- unique constraints\n- transactional outbox\n- workflow orchestration\n- explicit reconciliation\n\nA circuit breaker can stop you from continuing to call a failing payment provider, but it cannot make unsafe retries safe.\n\n## Circuit Breakers and Fallbacks\n\nWhen the circuit is open, your service needs a plan.\n\nPossible fallback strategies include:\n\n- return cached data\n- return stale data with a warning\n- omit a non-critical section\n- queue work for later\n- switch to another provider\n- return a temporary error\n- show degraded functionality\n- fail the request quickly\n\nA fallback should be honest.\n\nFor example, this is usually good:\n\n```\n{\n  \"status\": \"temporary_unavailable\",\n  \"message\": \"Recommendations are temporarily unavailable\"\n}\n```\n\nThis is risky:\n\n```\n{\n  \"recommendations\": []\n}\n```\n\nAn empty list may look like a valid result. It can hide an outage, confuse users, and make debugging harder.\n\nSilent fallbacks are tempting. They are also dangerous.\n\n## Circuit Breakers and Observability\n\nA circuit breaker without observability is mostly a surprise generator.\n\nTrack at least these metrics:\n\n- current breaker state\n- state changes\n- calls allowed\n- calls rejected\n- successes\n- failures\n- timeouts\n- fallback responses\n- retry attempts\n- downstream latency\n- downstream status codes\n\nUseful labels include:\n\n- breaker name\n- dependency name\n- operation name\n- status class\n- error category\n\nAvoid high-cardinality labels such as user ID, full URL, request ID, or raw error messages.\n\nYou should be able to answer these questions from dashboards:\n\n- Which circuit breakers are open right now?\n- How often do they open?\n- Which dependency caused the opening?\n- Are users seeing fallback responses?\n- Did latency improve after the breaker opened?\n- Did retry volume spike before the breaker opened?\n- Did the dependency recover?\n\nIf you cannot observe the breaker, you cannot tune it. For structured logging that pairs well with metrics, see [Structured Logging in Go with slog](https://www.glukhov.org/observability/logging/structured-logging-go-slog/).\n\n## A More Production-Friendly HTTP Client Shape\n\nFor real services, avoid scattering circuit breaker logic across handlers.\n\nCreate a small client package around the dependency.\n\nExample structure:\n\n```\ninternal/\n  userservice/\n    client.go\n    errors.go\n    metrics.go\n```\n\nThe handler should not know the details of gobreaker. It should depend on a domain-level client method:\n\n```\ntype UserService interface {\n    GetUser(ctx context.Context, userID string) (*User, error)\n}\n```\n\nThen the implementation can contain:\n\n- HTTP request creation\n- context propagation\n- breaker execution\n- status code handling\n- response decoding\n- metrics\n- error mapping\n\nThis keeps the resilience policy close to the dependency boundary. For more on error classification at boundaries, see [Go Error Handling Architecture: Boundaries and Patterns](https://www.glukhov.org/app-architecture/code-architecture/go-error-handling-architecture/).\n\n## Where Circuit Breakers Fit in Application Architecture\n\nThe circuit breaker pattern belongs at integration boundaries.\n\nIn a Go application, that usually means:\n\nKeep the breaker out of business logic when possible.\n\nThe business layer should understand domain errors like:\n\n```\npayment provider unavailable\nrecommendations unavailable\nprofile service timeout\n```\n\nIt should not need to understand gobreaker states.\n\nThis separation keeps the architecture clean:\n\n- transport concerns stay in clients\n- resilience policy stays near dependencies\n- domain logic stays readable\n- handlers stay thin\n- tests are easier to write\n\n## Common Mistakes\n\n### Mistake 1: No Timeout\n\nA circuit breaker does not magically stop slow calls unless the calls return.\n\nIf the outbound operation can hang forever, the breaker may not see a failure quickly enough.\n\nAlways use timeouts.\n\n### Mistake 2: One Global Breaker for Everything\n\nDo not use one breaker for all dependencies.\n\nA failing email provider should not open the circuit for your payment provider. A slow search endpoint should not block user profile calls.\n\nUse separate breakers for separate dependency operations when their failure modes differ.\n\n### Mistake 3: Counting Caller Errors as Dependency Failures\n\nIf your service sends bad input and receives `400 Bad Request`\n\n, that is usually not a downstream outage.\n\nDo not train the breaker on your own bugs.\n\n### Mistake 4: Retrying Non-Idempotent Writes\n\nRetries are not free. They can duplicate writes, payments, messages, or side effects.\n\nMake writes idempotent before retrying them.\n\n### Mistake 5: Hiding Outages Behind Fallbacks\n\nFallbacks should degrade gracefully, not falsify reality.\n\nIf a dependency is down, your metrics and logs should make that obvious.\n\n### Mistake 6: Tuning Without Production Data\n\nThresholds copied from examples are only starting points.\n\nTune based on:\n\n- request volume\n- normal error rate\n- dependency latency\n- user impact\n- recovery time\n- fallback quality\n\n### Mistake 7: Using Circuit Breakers Instead of Capacity Management\n\nA circuit breaker is not a substitute for:\n\n- load shedding\n- rate limiting\n- queue limits\n- autoscaling\n- database tuning\n- connection pool limits\n- upstream quotas\n\nIt is one part of a resilience strategy.\n\n## Practical Defaults\n\nFor a typical Go service calling an internal HTTP dependency, a reasonable starting point might be:\n\n```\nHTTP client timeout: 2 to 5 seconds\nper-request context timeout: based on caller SLA\nbreaker failure rule: 5 consecutive failures or 50 percent failure after 20 requests\nopen timeout: 10 to 30 seconds\nhalf-open requests: 1 to 5\nretry count: 1 to 3 attempts\nretry backoff: exponential with jitter\n```\n\nThese are not universal values. They are safe-ish starting points.\n\nFor user-facing APIs, keep total latency budgets tight. For background jobs, you may tolerate longer waits. For payment providers, be much more careful with retries and idempotency.\n\n## Circuit Breaker Checklist\n\nBefore adding a circuit breaker, answer these questions:\n\n- What dependency is being protected?\n- What operation is being protected?\n- What errors count as dependency failure?\n- What errors should be ignored by the breaker?\n- What timeout applies to each call?\n- Are retries allowed?\n- Are writes idempotent?\n- What happens when the circuit is open?\n- Is there a fallback?\n- Is the fallback visible in metrics?\n- Who gets alerted if the circuit keeps opening?\n- How will the breaker be tuned after deployment?\n\nIf you cannot answer these, adding a breaker may create more confusion than resilience.\n\n## Testing Circuit Breakers in Go\n\nTest behavior, not the internal state machine of the library.\n\nUseful tests include:\n\n- dependency succeeds and response is returned\n- dependency fails repeatedly and circuit opens\n- open circuit returns a temporary error\n- client-side validation errors do not trip the breaker\n- context timeout is respected\n- fallback response is returned when expected\n- metrics are emitted on state changes\n\nUse fake HTTP servers for integration-style tests:\n\n```\nserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    http.Error(w, \"unavailable\", http.StatusServiceUnavailable)\n}))\ndefer server.Close()\n```\n\nFor unit tests, hide the dependency behind an interface and inject a fake implementation.\n\nKeep tests deterministic. Avoid sleeping for long real durations. Configure short breaker timeouts in tests. For more on testing concurrent Go code with fake time and isolated bubbles, see [Testing Concurrent Go Code with testing/synctest](https://www.glukhov.org/app-architecture/testing-architecture/testing-concurrent-go-code-synctest/).\n\n## Should You Build Your Own Circuit Breaker?\n\nBuilding a small circuit breaker is a good learning exercise. It helps you understand the state machine.\n\nFor production code, prefer a maintained library unless your needs are very specific.\n\nA production breaker needs to handle:\n\n- concurrency\n- state transitions\n- counters\n- half-open probes\n- callbacks\n- custom failure classification\n- race-free behavior\n- predictable error handling\n\nThat is not impossible, but it is easy to get subtly wrong.\n\nThe boring library is usually the better choice.\n\n## Conclusion\n\nThe circuit breaker pattern is not magic reliability dust.\n\nIn Go, it works best when it is part of a small, explicit resilience stack:\n\n```\ncontext timeout\n+ retry with backoff and jitter\n+ circuit breaker\n+ fallback\n+ metrics\n```\n\nThe pattern is most useful at dependency boundaries, especially around remote services that can become slow or partially unavailable.\n\nUse it to stop cascading failures. Use it to fail fast when a dependency is clearly unhealthy. Use it to give overloaded systems room to recover.\n\nBut do not use it as an excuse to ignore timeouts, idempotency, observability, or clean architecture.\n\nA good circuit breaker makes failure clearer and cheaper. A bad one just makes failure more mysterious.\n\n## References\n\n[Go Microservices for AI/ML Orchestration](https://www.glukhov.org/app-architecture/integration-patterns/go-microservices-for-ai-ml-orchestration-patterns/)— broader orchestration context where circuit breakers fit[Saga Pattern in Distributed Transactions](https://www.glukhov.org/app-architecture/integration-patterns/saga-transactions-in-microservices/)— distributed transaction patterns that pair with circuit breakers[Idempotency in Distributed Systems](https://www.glukhov.org/app-architecture/integration-patterns/idempotency-in-distributed-systems/)— retry safety and idempotent operations[Transactional Outbox Pattern in Go](https://www.glukhov.org/app-architecture/integration-patterns/transactional-outbox-pattern-go/)— reliable event delivery alongside resilience patterns[Go Error Handling Architecture](https://www.glukhov.org/app-architecture/code-architecture/go-error-handling-architecture/)— error classification at dependency boundaries[Testing Concurrent Go Code with synctest](https://www.glukhov.org/app-architecture/testing-architecture/testing-concurrent-go-code-synctest/)— testing async behavior with circuit breakers[Structured Logging in Go with slog](https://www.glukhov.org/observability/logging/structured-logging-go-slog/)— observability alongside circuit breakers`github.com/sony/gobreaker/v2`\n\n— official gobreaker v2 package[Go Context Cancellation and Timeouts](https://www.glukhov.org/app-architecture/code-architecture/go-context-cancellation-timeouts/)— context patterns that pair with circuit breakers", "url": "https://wpnews.pro/news/circuit-breaker-pattern-in-go-stop-cascading-failures", "canonical_source": "https://www.glukhov.org/app-architecture/integration-patterns/circuit-breaker-pattern-in-go/", "published_at": "2026-07-13 00:00:00+00:00", "updated_at": "2026-07-13 12:09:45.909012+00:00", "lang": "en", "topics": ["developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/circuit-breaker-pattern-in-go-stop-cascading-failures", "markdown": "https://wpnews.pro/news/circuit-breaker-pattern-in-go-stop-cascading-failures.md", "text": "https://wpnews.pro/news/circuit-breaker-pattern-in-go-stop-cascading-failures.txt", "jsonld": "https://wpnews.pro/news/circuit-breaker-pattern-in-go-stop-cascading-failures.jsonld"}}