Circuit Breaker

Level: Advanced 60–90 min

Concepts: 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.

  1. 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
  2. Open (failing fast)

    • All requests fail immediately without calling the service
    • After the reset timeout expires, transition to Half-Open
  3. 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:

StepStateActionService ResultCircuit Breaker ResultNew State
1ClosedcallsuccesssuccessClosed
2ClosedcallfailurefailureClosed (1 fail)
3ClosedcallfailurefailureClosed (2 fails)
4ClosedcallfailurefailureOpen (3 fails = threshold)
5Opencallfail immediatelyOpen
6Open +30scallsuccesssuccessClosed
7Open +30scallfailurefailureOpen (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 onStateChange callback 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, () => T operation) — 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 rationalewhy 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.


  • 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.