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.

Medium Python 3.9+ Aug 9, 2026 Reliability & rate limiting 15 views 0 copies

Python code

55 lines
Python 3.9+
from 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

stdout
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

  1. Use a threading.Lock to make the circuit breaker thread-safe for concurrent usage
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Reliability & rate limiting

Related tutorials and quizzes for this topic.