On this page
What Is the Circuit Breaker Pattern?
A circuit breaker stops your app from hammering a failing dependency. Learn the three states, why backends fall over, and when to add one.
Quick answer
- A circuit breaker wraps calls to a dependency and trips after repeated failures, so your app fails fast instead of waiting on timeouts.
- It has three states: closed (normal), open (failing, don’t call), and half-open (probing with a test request).
- It protects both sides: your app doesn’t pile up slow requests, and the struggling dependency doesn’t get hammered by retries.
Why do failing dependencies take down the whole app?
When a dependency slows down — a database that’s overloaded or an API that’s degraded — every request to your app waits on it. With no protection, connections pile up, threads block, and your app exhausts its connection pool and starts failing for reasons unrelated to the dependency. This is cascading failure: one slow service takes down every service that calls it. Timeouts help, but a timeout of 10 seconds still means 10 seconds of blocked resources per request.
How does a circuit breaker work?
It tracks failures on calls to one dependency. When failures cross a threshold (say, 5 failures in 30 seconds), the breaker opens: subsequent calls fail immediately with an error, no attempt made. After a cooldown, it moves to half-open and lets a single test request through; if that succeeds, it closes and traffic flows again; if it fails, it opens again. The dependency gets time to recover without being hammered, and your app fails fast instead of hanging.
# A minimal breaker: track failures, trip, probe, recover
import time
class CircuitBreaker:
def __init__(self, threshold=5, cooldown=30):
self.threshold, self.cooldown = threshold, cooldown
self.failures, self.open_until, self.state = 0, 0, "closed"
def call(self, fn):
if self.state == "open" and time.time() < self.open_until:
raise RuntimeError("circuit open — failing fast")
try:
result = fn()
self.failures, self.state = 0, "closed"
return result
except Exception:
self.failures += 1
if self.failures >= self.threshold:
self.state, self.open_until = "open", time.time() + self.cooldown
raiseWhen should I add a circuit breaker?
When your app calls a dependency that can fail or slow down independently — a third-party API, a database, another service — and you can’t afford to hang every request on it. For a single small app with one database, a connection pool with short timeouts may be enough. Circuit breakers earn their complexity in front of flaky external APIs and in service-to-service calls where cascading failure is a real risk. A fallback response (stale cache, default data) makes the breaker genuinely useful.
Where this bites vibecoders
The AI-generated app that calls an LLM API with a 60-second timeout is a circuit breaker waiting to happen: when the provider degrades, every user request blocks for a minute, the process exhausts its thread pool, and the whole app is down. The assistant’s instinct is to ‘add more retries’, which makes it worse. A breaker with a fast fallback (‘LLM unavailable, here’s the cached summary’) keeps the app alive through a provider outage.
Where AI coding assistants get this wrong
- Adding more retries to a failing dependency, extending the outage instead of ending it.
- Timeouts set so long that one slow dependency blocks the whole request pipeline.
- No fallback, so a broken dependency takes the entire app down with it.
- A breaker with no state visibility, so you can’t tell why requests are failing fast.
Checklist
- Add a circuit breaker around any dependency that can degrade independently.
- Set realistic timeouts (2-5s) so the breaker has something to trip on.
- Provide a fallback: cached data, defaults, or a clear error page.
- Expose breaker state in metrics so you can see it trip and recover.
FAQ
What is the difference between a circuit breaker and a retry?
Retries handle transient failures on a single call. A circuit breaker manages the relationship with a dependency over time — it stops calling entirely when the dependency is clearly failing. They complement each other: retry a few times, and let the breaker stop the calls when retries keep failing.
How many failures should trip the breaker?
There’s no universal number; a common starting point is 5 failures within 30 seconds, or 50% of calls failing in a window. What matters is that the threshold catches real degradation quickly and doesn’t trip on rare blips. Monitor false trips and tune from there.
Related topics
- What Is Exponential Backoff?
- How to Add Retry Logic to API Calls
- What Is Chaos Engineering?
- What Is Observability (and How Is It Different From Monitoring)?