Circuit Breaker
Level: Advanced 60–90 minConcepts: Design PatternsStateMockingBoundariesEdge Cases
Solutions: C# | TypeScript | Python
Implement the circuit breaker pattern to protect against cascading failures when calling unreliable external services.
Requirements
A circuit breaker has three states: Closed, Open, and Half-Open.
-
Closed (normal operation)
- Requests pass through to the underlying service
- Track consecutive failures
- When failures reach the failure threshold, transition to Open
- Successes reset the failure counter
-
Open (failing fast)
- All requests fail immediately without calling the service
- After the reset timeout expires, transition to Half-Open
-
Half-Open (testing recovery)
- Allow a single request through to the service
- If it succeeds, transition to Closed and reset counters
- If it fails, transition back to Open and restart the timeout
Configuration:
failureThreshold— number of consecutive failures before opening (default: 3)resetTimeout— seconds to wait before transitioning from Open to Half-Open (default: 30)
The circuit breaker should accept an injectable clock for testability.
Test Cases
Given a circuit breaker with threshold=3 and timeout=30s:
| Step | State | Action | Service Result | Circuit Breaker Result | New State |
|---|---|---|---|---|---|
| 1 | Closed | call | success | success | Closed |
| 2 | Closed | call | failure | failure | Closed (1 fail) |
| 3 | Closed | call | failure | failure | Closed (2 fails) |
| 4 | Closed | call | failure | failure | Open (3 fails = threshold) |
| 5 | Open | call | — | fail immediately | Open |
| 6 | Open +30s | call | success | success | Closed |
| 7 | Open +30s | call | failure | failure | Open (restart timeout) |
Additional cases:
- Success in Closed state resets failure count to 0
- Multiple successes after failures keep the counter at 0
- Open state does not call the underlying service at all
- Half-Open allows exactly one request through
Bonus
- Add an
onStateChangecallback that fires when the state transitions - Add
getState()to inspect the current state - Add
getMetrics()returning success count, failure count, and rejection count - Implement a half-open quota — allow N requests through in half-open instead of just 1
- Add exponential backoff — double the reset timeout after each Open→Half-Open→Open cycle
Reference Walkthrough
Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/circuit-breaker. Twenty scenarios across construction, Closed state behaviour, Open state fail-fast, Open → HalfOpen timeout elapse, HalfOpen probe success/failure, and end-to-end round-trips — shared across all three languages — with a Breaker aggregate, BreakerState enum, Clock collaborator, BreakerBuilder + FixedClock test doubles, and named domain exceptions (BreakerThresholdInvalidException / BreakerTimeoutInvalidException / CircuitOpenException).
- C# (.NET 8, xUnit, FluentAssertions,
Func<T>operation) — walkthrough - TypeScript (Node 20, Vitest, strict types,
() => Toperation) — walkthrough - Python (3.11, pytest,
Callable[[], T], Protocol clock) — walkthrough
This kata ships in Agent Full-Bake (F3) mode: one commit per language with the full domain design landing together. The walkthroughs read as design rationale — why execute takes a callable (the breaker needs to observe the call, not a pre-computed result), why BreakerState is a flat enum rather than Gang-of-Four state-object classes (the state machine has three states and shares fields across them — the pattern would add ceremony without encapsulation), and why the Open → HalfOpen check is lazy on the next execute rather than a background tick (same shape as memory-cache’s lazy TTL expiry — no threading, deterministic tests). The reference scopes to the twenty core scenarios; the bonus items (onStateChange callbacks, getMetrics, half-open quota > 1, exponential backoff) are deliberately out of scope — each has a natural insertion point in the design without reshaping it. See the repo’s Gears section for when middle gear is the right call.
Related reading
- Examples Pin Intent. Properties Pin the Invariants.
Example-based tests anchor scenarios in the team's vocabulary. Property-based tests anchor invariants across the input space. The agent needs both axes, and most suites only carry one. - The Test Pyramid Was an Economic Argument
The test pyramid was not a quality law. It was a cost structure: unit tests were cheap, integration tests were expensive, so you wrote many of the first and few of the second. Agents collapsed the cost of writing tests at every level, and the cheapest test that still tells the truth is the one that pins a seam the agent cannot fake. - Agents Amplify Whatever Vocabulary They Find
Agents do not bring vocabulary to your codebase. They mirror what's already there. Strong vocabulary gets agent contributions that read like the team. Weak vocabulary gets faster mediocrity. The work you did for craft reasons just became leverage.