How to Implement a Circuit Breaker in Python
A Python dataclass that provides circuit breaker logic with closed, open, and half-open states to fail fast on repeated errors.
Python code
55 linesfrom dataclasses import dataclass
from datetime import datetime, timedelta
import time
@dataclass
class CircuitBreaker:
failure_threshold: int = 3
timeout_seconds: float = 5.0
failures: int = 0
state: str = "closed"
last_failure: datetime = None
def call(self, func):
if self.state == "open":
if datetime.now() - self.last_failure > timedelta(seconds=self.timeout_seconds):
print("Circuit half-open: testing recovery")
self.state = "half_open"
else:
raise Exception("Circuit is open - fast fail")
try:
result = func()
if self.state == "half_open":
print("Success in half-open: closing circuit")
self.state = "closed"
self.failures = 0
return result
except Exception as e:
self.failures += 1
self.last_failure = datetime.now()
if self.failures >= self.failure_threshold:
self.state = "open"
print(f"Circuit open after {self.failures} failures")
raise e
def reset(self):
self.failures = 0
self.state = "closed"
if __name__ == "__main__":
breaker = CircuitBreaker(failure_threshold=2, timeout_seconds=1.0)
def flaky_service():
if datetime.now().second % 2 == 0:
raise ValueError("Transient error")
return "ok"
for attempt in range(6):
try:
print(f"Attempt {attempt}: {breaker.call(flaky_service)}")
except Exception as e:
print(f"Attempt {attempt}: raised {type(e).__name__}: {e}")
time.sleep(0.3)
Output
Attempt 0: raised ValueError: Transient error
Circuit open after 2 failures
Attempt 1: raised ValueError: Transient error
Attempt 2: raised Exception: Circuit is open - fast fail
Attempt 3: raised Exception: Circuit is open - fast fail
Circuit half-open: testing recovery
Attempt 4: ok
Success in half-open: closing circuit
Attempt 5: ok
How it works
The CircuitBreaker dataclass tracks the number of consecutive failures and switches to the open state once the threshold is reached. In the open state, calls fail immediately without invoking the underlying function, providing fast failure. After the timeout period elapses, the state changes to half_open, allowing a single trial call. A successful call in half_open resets the circuit to closed, while a failure reopens it. This pattern prevents cascading failures in distributed systems.
Common mistakes
- Resetting the failure count after every failure instead of only after a successful call
- Not using a timeout to transition from open to half-open, causing permanent circuit opening
- Raising the original exception instead of a custom fast-fail exception when circuit is open
- Forgetting to update `last_failure` when the circuit opens
Variations
- Use a threading.Lock to make the circuit breaker thread-safe for concurrent usage
- Add a jitter to the timeout to avoid thundering herd when multiple callers retry simultaneously
Real-world use cases
- Prevent a downstream payment API from being overwhelmed when it starts failing intermittently.
- Wrap a third-party rate-limited API to fail fast and avoid burning quota during outages.
- Add resilience to a microservice calling a legacy database that experiences slow or failed queries.
Sponsored
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a Rate Limiter Decorator in Python easy
- Build a queue-based admission control system in Python easy
- Chaos Inject Random Failures in Python easy
- Circuit breaker failure threshold count in Python medium
- Exactly Once Processing Dedupe Mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.