# Circuit Breaker Pattern

## Problem

When a remote service is failing, continuing to send requests wastes resources, increases latency, and can cascade failures through the system. Without protection, callers pile up waiting for responses that will never arrive.

## Solution

Wrap calls in a state machine with three states:

- **Closed** — Requests flow normally. Failures are counted. When the failure threshold is exceeded, transition to Open.
- **Open** — All requests are immediately rejected (fail fast). After a timeout period, transition to Half-Open.
- **Half-Open** — Allow a limited number of probe requests. If they succeed, transition to Closed. If they fail, return to Open.

## When to Use

- Calling external APIs or microservices that may become unavailable
- Database connections that can timeout under load
- Any remote dependency with non-trivial failure rates
- Systems where cascading failure is a risk

## When to Avoid

- In-process function calls that don't involve I/O
- Operations where every attempt must be made (use retry instead)
- Very low-traffic systems where the overhead isn't justified
- When failures are always permanent (circuit breaker won't help)

## Pseudocode

```
class CircuitBreaker:
    state = CLOSED
    failure_count = 0
    last_failure_time = null

    function call(operation):
        if state == OPEN:
            if now() - last_failure_time > recovery_timeout:
                state = HALF_OPEN
            else:
                raise CircuitOpenError

        try:
            result = operation()
            on_success()
            return result
        catch error:
            on_failure()
            raise error

    function on_success():
        failure_count = 0
        state = CLOSED

    function on_failure():
        failure_count += 1
        last_failure_time = now()
        if failure_count >= threshold:
            state = OPEN
```

## Python Implementation

```python
import time
from enum import Enum
from typing import Callable, Any


class State(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"


class CircuitOpenError(Exception):
    pass


class CircuitBreaker:
    def __init__(self, failure_threshold: int = 3, recovery_timeout: float = 5.0):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.state = State.CLOSED
        self.failure_count = 0
        self.last_failure_time: float = 0

    def call(self, operation: Callable[[], Any]) -> Any:
        if self.state == State.OPEN:
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = State.HALF_OPEN
                print(f"  [CB] Transitioning to HALF_OPEN")
            else:
                raise CircuitOpenError("Circuit is OPEN — failing fast")

        try:
            result = operation()
            self._on_success()
            return result
        except CircuitOpenError:
            raise
        except Exception as exc:
            self._on_failure()
            raise exc

    def _on_success(self):
        if self.state == State.HALF_OPEN:
            print(f"  [CB] Probe succeeded — transitioning to CLOSED")
        self.failure_count = 0
        self.state = State.CLOSED

    def _on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.failure_count >= self.failure_threshold:
            self.state = State.OPEN
            print(f"  [CB] Threshold reached — transitioning to OPEN")


# --- Demo ---
call_count = 0

def unreliable_service():
    global call_count
    call_count += 1
    if call_count <= 4:
        raise ConnectionError(f"Service down (attempt {call_count})")
    return f"OK (attempt {call_count})"


cb = CircuitBreaker(failure_threshold=3, recovery_timeout=1.0)

for i in range(1, 8):
    if i == 5:
        print("  Waiting for recovery timeout...")
        time.sleep(1.5)
    try:
        result = cb.call(unreliable_service)
        print(f"Call {i}: {result} [state={cb.state.value}]")
    except CircuitOpenError as e:
        print(f"Call {i}: BLOCKED — {e} [state={cb.state.value}]")
    except ConnectionError as e:
        print(f"Call {i}: FAILED — {e} [state={cb.state.value}]")
```

**Output:**
```
Call 1: FAILED — Service down (attempt 1) [state=closed]
Call 2: FAILED — Service down (attempt 2) [state=closed]
  [CB] Threshold reached — transitioning to OPEN
Call 3: FAILED — Service down (attempt 3) [state=open]
Call 4: BLOCKED — Circuit is OPEN — failing fast [state=open]
  Waiting for recovery timeout...
  [CB] Transitioning to HALF_OPEN
  [CB] Probe succeeded — transitioning to CLOSED
Call 5: OK (attempt 5) [state=closed]
Call 6: OK (attempt 6) [state=closed]
Call 7: OK (attempt 7) [state=closed]
```

## Go Implementation

```go
package main

import (
	"errors"
	"fmt"
	"sync"
	"time"
)

type State int

const (
	Closed State = iota
	Open
	HalfOpen
)

func (s State) String() string {
	return [...]string{"closed", "open", "half_open"}[s]
}

var ErrCircuitOpen = errors.New("circuit is OPEN — failing fast")

type CircuitBreaker struct {
	mu               sync.Mutex
	state            State
	failureCount     int
	failureThreshold int
	recoveryTimeout  time.Duration
	lastFailureTime  time.Time
}

func NewCircuitBreaker(threshold int, timeout time.Duration) *CircuitBreaker {
	return &CircuitBreaker{
		failureThreshold: threshold,
		recoveryTimeout:  timeout,
		state:            Closed,
	}
}

func (cb *CircuitBreaker) Call(operation func() (string, error)) (string, error) {
	cb.mu.Lock()
	if cb.state == Open {
		if time.Since(cb.lastFailureTime) > cb.recoveryTimeout {
			cb.state = HalfOpen
			fmt.Println("  [CB] Transitioning to HALF_OPEN")
		} else {
			cb.mu.Unlock()
			return "", ErrCircuitOpen
		}
	}
	cb.mu.Unlock()

	result, err := operation()
	cb.mu.Lock()
	defer cb.mu.Unlock()
	if err != nil {
		cb.failureCount++
		cb.lastFailureTime = time.Now()
		if cb.failureCount >= cb.failureThreshold {
			cb.state = Open
			fmt.Println("  [CB] Threshold reached — transitioning to OPEN")
		}
		return "", err
	}
	if cb.state == HalfOpen {
		fmt.Println("  [CB] Probe succeeded — transitioning to CLOSED")
	}
	cb.failureCount = 0
	cb.state = Closed
	return result, nil
}

func main() {
	callCount := 0
	service := func() (string, error) {
		callCount++
		if callCount <= 4 {
			return "", fmt.Errorf("service down (attempt %d)", callCount)
		}
		return fmt.Sprintf("OK (attempt %d)", callCount), nil
	}

	cb := NewCircuitBreaker(3, 1*time.Second)

	for i := 1; i <= 7; i++ {
		if i == 5 {
			fmt.Println("  Waiting for recovery timeout...")
			time.Sleep(1500 * time.Millisecond)
		}
		result, err := cb.Call(service)
		if err != nil {
			fmt.Printf("Call %d: FAILED — %v [state=%s]\n", i, err, cb.state)
		} else {
			fmt.Printf("Call %d: %s [state=%s]\n", i, result, cb.state)
		}
	}
}
```

## JavaScript Implementation

```javascript
class CircuitBreaker {
  constructor(failureThreshold = 3, recoveryTimeoutMs = 5000) {
    this.failureThreshold = failureThreshold;
    this.recoveryTimeoutMs = recoveryTimeoutMs;
    this.state = "closed";
    this.failureCount = 0;
    this.lastFailureTime = 0;
  }

  async call(operation) {
    if (this.state === "open") {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeoutMs) {
        this.state = "half_open";
        console.log("  [CB] Transitioning to HALF_OPEN");
      } else {
        throw new Error("Circuit is OPEN — failing fast");
      }
    }

    try {
      const result = await operation();
      if (this.state === "half_open") {
        console.log("  [CB] Probe succeeded — transitioning to CLOSED");
      }
      this.failureCount = 0;
      this.state = "closed";
      return result;
    } catch (err) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      if (this.failureCount >= this.failureThreshold) {
        this.state = "open";
        console.log("  [CB] Threshold reached — transitioning to OPEN");
      }
      throw err;
    }
  }
}

// --- Demo ---
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

let callCount = 0;
function unreliableService() {
  callCount++;
  if (callCount <= 4) throw new Error(`Service down (attempt ${callCount})`);
  return `OK (attempt ${callCount})`;
}

(async () => {
  const cb = new CircuitBreaker(3, 1000);

  for (let i = 1; i <= 7; i++) {
    if (i === 5) {
      console.log("  Waiting for recovery timeout...");
      await sleep(1500);
    }
    try {
      const result = await cb.call(() => unreliableService());
      console.log(`Call ${i}: ${result} [state=${cb.state}]`);
    } catch (err) {
      console.log(`Call ${i}: FAILED — ${err.message} [state=${cb.state}]`);
    }
  }
})();
```

## Related Patterns

- **Proxy** — Circuit Breaker is most cleanly implemented as a Proxy that wraps external calls and intercepts them based on state.
- **Retry** — Circuit Breaker and Retry work together: Retry for transient errors; Circuit Breaker trips when errors become systemic.
- **Fallback** — When Circuit Breaker is open, Fallback provides degraded functionality.
- **Observer** — State transitions (Closed→Open→Half-Open) should emit events via Observer for monitoring and alerting.
- **Bulkhead** — Circuit Breaker isolates by failure rate; Bulkhead isolates by resource pool. Use both for defense in depth.
