Circuit Breaker Pattern
Prevent cascading failures by stopping requests to failing services. An architectural pattern for resilient distributed systems.
Overview
The Circuit Breaker Pattern is an architectural pattern that prevents an application from repeatedly trying to execute an operation that is likely to fail. When a service is down or struggling, the circuit breaker “trips” and stops sending requests, giving the service time to recover. This prevents cascading failures and resource exhaustion in distributed systems.
When to Use
Use the Circuit Breaker Pattern when:
- A service call to a remote dependency may fail or timeout
- You want to prevent cascading failures across services
- You need to degrade gracefully when a service is unavailable
- You want to avoid overwhelming a failing service with retries
- You need fast failure for calls to unhealthy downstream services
Solution
Python
import time
from enum import Enum, auto
class CircuitState(Enum):
CLOSED = auto() # Normal operation
OPEN = auto() # Failing fast
HALF_OPEN = auto() # Testing recovery
class CircuitBreaker:
def __init__(self, failure_threshold=3, recovery_timeout=5):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.state = CircuitState.CLOSED
self.failures = 0
self.last_failure_time = None
def call(self, func, *args, **kwargs):
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time >= self.recovery_timeout:
self.state = CircuitState.HALF_OPEN
else:
raise Exception("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise e
def _on_success(self):
self.failures = 0
self.state = CircuitState.CLOSED
def _on_failure(self):
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPEN
# Usage
def fetch_data():
# Simulated flaky service
import random
if random.random() < 0.7:
raise Exception("Service error")
return "Data"
breaker = CircuitBreaker(failure_threshold=2, recovery_timeout=3)
for i in range(5):
try:
print(breaker.call(fetch_data))
except Exception as e:
print(f"Call {i+1}: {e}")
JavaScript
class CircuitBreaker {
constructor(failureThreshold = 3, recoveryTimeout = 5000) {
this.failureThreshold = failureThreshold;
this.recoveryTimeout = recoveryTimeout;
this.state = 'CLOSED';
this.failures = 0;
this.lastFailureTime = null;
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime >= this.recoveryTimeout) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (e) {
this.onFailure();
throw e;
}
}
onSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
}
}
}
// Usage
async function fetchData() {
if (Math.random() < 0.7) throw new Error('Service error');
return 'Data';
}
const breaker = new CircuitBreaker(2, 3000);
(async () => {
for (let i = 0; i < 5; i++) {
try {
const result = await breaker.call(fetchData);
console.log(`Call ${i + 1}:`, result);
} catch (e) {
console.log(`Call ${i + 1}:`, e.message);
}
}
})();
Java
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class CircuitBreaker {
private enum State { CLOSED, OPEN, HALF_OPEN }
private final int failureThreshold;
private final long recoveryTimeoutMs;
private State state = State.CLOSED;
private final AtomicInteger failures = new AtomicInteger(0);
private volatile long lastFailureTime = 0;
public CircuitBreaker(int failureThreshold, long recoveryTimeoutMs) {
this.failureThreshold = failureThreshold;
this.recoveryTimeoutMs = recoveryTimeoutMs;
}
public <T> T call(Supplier<T> supplier) throws Exception {
if (state == State.OPEN) {
if (System.currentTimeMillis() - lastFailureTime >= recoveryTimeoutMs) {
state = State.HALF_OPEN;
} else {
throw new Exception("Circuit breaker is OPEN");
}
}
try {
T result = supplier.get();
onSuccess();
return result;
} catch (Exception e) {
onFailure();
throw e;
}
}
private void onSuccess() {
failures.set(0);
state = State.CLOSED;
}
private void onFailure() {
int count = failures.incrementAndGet();
lastFailureTime = System.currentTimeMillis();
if (count >= failureThreshold) {
state = State.OPEN;
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
CircuitBreaker breaker = new CircuitBreaker(2, 3000);
for (int i = 0; i < 5; i++) {
try {
String result = breaker.call(() -> {
if (Math.random() < 0.7) throw new RuntimeException("Service error");
return "Data";
});
System.out.println("Call " + (i + 1) + ": " + result);
} catch (Exception e) {
System.out.println("Call " + (i + 1) + ": " + e.getMessage());
}
}
}
}
Explanation
The Circuit Breaker Pattern has three states:
- Closed — Normal operation. Requests pass through to the service. Failures are counted.
- Open — The service is considered unhealthy. All requests fail fast immediately without calling the service.
- Half-Open — After a timeout, a limited number of test requests are allowed through to check if the service recovered.
This prevents resource exhaustion from repeated failed calls and gives failing services time to recover. Essential in distributed systems.
Variants
| Variant | Behavior | Use Case |
|---|---|---|
| Count-based | Trip after N failures | Simple, predictable behavior |
| Time-based | Trip if failure rate exceeds threshold in a time window | Adapts to varying load |
| Weighted | Different failure thresholds for different exception types | Distinguish transient vs. permanent failures |
| Custom Fallback | Return a default value when open instead of throwing | Graceful degradation (cache, default response) |
What Works
- Configure recovery timeouts based on the service’s typical restart time
- Log state transitions for observability and alerting
- Provide fallback behavior when the circuit is open (cached data, default response)
- Use separate circuit breakers for different downstream services
- Avoid sharing circuit breaker state across unrelated operations
Common Mistakes
- Setting the failure threshold too low, causing frequent false positives
- Setting the recovery timeout too short, not giving the service enough time to recover
- Using a single circuit breaker for all operations, causing unnecessary broad outages
- Not providing fallback behavior, leading to poor user experience when circuits are open
- Ignoring half-open state, never allowing recovery testing after a failure
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Key Takeaways
- Apply circuit breaker pattern when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
Common Production Pitfalls
- Applying the pattern where no abstraction is needed, adding accidental complexity.
- Letting the pattern leak into unrelated modules and blur ownership boundaries.
- Over-engineering the first implementation instead of starting simple and measuring pain.
- Skipping contract tests, so refactors silently break consumers.
- Ignoring failure modes that the pattern does not cover.
- Using the pattern as a default instead of choosing the right tool for the current scale.
- Forgetting to document when to stop using the pattern and what replaces it.
- Missing observability around the pattern’s performance and error propagation.
Frequently Asked Questions
How is Circuit Breaker different from Retry?
Retry attempts the same operation multiple times. Circuit Breaker stops calling a failing service altogether. They work well together: retry for transient failures, circuit breaker for persistent outages.
Should I use a library or implement my own?
For production systems, use established libraries: Resilience4j (Java), Polly (.NET), Opossum (JavaScript/Node). See also Ambassador for client-side resilience. Implement your own only for learning or very constrained environments.
How do I monitor circuit breaker health?
Expose metrics for state transitions, failure rates, and open duration. Integrate with your monitoring stack (Prometheus, Grafana) to alert on frequent circuit trips.
Related Resources
Circuit Breaker with Monitoring
How to expose circuit breaker state as metrics for observability. Covers Prometheus integration, alerting rules, dashboards, and state transitions.
PatternProxy Pattern
Provide a surrogate or placeholder for another object to control access to it. A structural design pattern for access control, lazy loading, and logging.
PatternObserver Pattern
Define a subscription mechanism to notify multiple objects about events. A behavioral design pattern for event-driven communication.
PatternStrategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.
PatternBack-Pressure Pattern
Prevent upstream systems from overwhelming downstream consumers by propagating flow-control signals backward through the pipeline, ensuring stable throughput under load.
PatternRetry Pattern
Retry an operation that has failed with transient errors, using configurable strategies like fixed delay, exponential backoff, or circuit breaker integration.