Circuit Breaker

How to protect your services from cascading failures using the Circuit Breaker pattern with states, fallbacks, and automatic recovery.

What problem it solves

In a microservices architecture, services depend on one another. When a downstream service becomes slow or stops responding, the services calling it accumulate open connections, exhaust their thread pools, and eventually fail as well. This domino effect is known as a cascading failure.

Example of the problem

Usuario β†’ API Gateway β†’ Servicio A β†’ Servicio B (lento/caΓ­do)
                                         ⏳ timeout 30s
                                         ⏳ timeout 30s
                                         ⏳ timeout 30s
                         ↓
              Servicio A agota sus threads
              esperando respuesta de B
                         ↓
              API Gateway agota sus threads
              esperando respuesta de A
                         ↓
              Todo el sistema deja de responder

Without protection, a single slow service can take down the entire platform. The Circuit Breaker acts as an automatic switch that cuts off communication with a failing service, preventing the problem from spreading.

The pattern was popularized by Michael Nygard in his book Release It! and takes its name from the electrical circuit breakers that cut the circuit when they detect an overload.

Real impact of cascading failures

MetricWithout Circuit BreakerWith Circuit Breaker
Detection timeMinutes (when users report it)Seconds (circuit state change)
Affected servicesAll that depend on the failed serviceOnly the failed service
RecoveryManual (restart services, clear queues)Automatic (half-open detects recovery)
User experienceLong timeouts, 500 errorsFast response with fallback or clear error
Resources consumedThreads and connections exhaustedResources released immediately

How it works

The Circuit Breaker is a proxy placed between a service and its dependencies. It monitors the calls and, based on the failure rate, decides whether to allow or block requests.

The three states

                    Fallos > umbral
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” ──────────────────► β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ CERRADO  β”‚                     β”‚ ABIERTO  β”‚
    β”‚ (normal) β”‚ ◄──────────────── β”‚ (cortado)β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   Prueba exitosa    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
         β–²                                β”‚
         β”‚         Timeout expira         β”‚
         β”‚                                β–Ό
         β”‚                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         └────── Prueba exitosa ────│SEMI-ABIERTOβ”‚
                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  Prueba falla ────► ABIERTO

Closed state

This is the normal state. All requests pass through to the downstream service. The circuit breaker monitors the results:

  • It counts recent failures (errors, timeouts)
  • If the failure rate exceeds a threshold (e.g., 50% failures in the last 10 calls), it switches to Open

Open state

The circuit is cut. Requests do not reach the downstream service. Instead, the circuit breaker responds immediately with an error or executes a fallback.

  • It avoids sending traffic to a service we know is failing
  • It releases resources (threads, connections) that would be wasted waiting for timeouts
  • After a wait period (e.g., 30 seconds), it switches to Half-Open

Half-Open state

A test state. The circuit breaker lets a limited number of requests through to check whether the service has recovered:

  • If the test requests succeed β†’ it returns to Closed
  • If the test requests fail β†’ it returns to Open

Configuration parameters

ParameterDescriptionExampleConsideration
Failure thresholdPercentage of failures to open50%Too low = false positives, too high = slow reaction
Request volumeMinimum requests before evaluating10 requestsPrevents opening the circuit on 1 isolated failure
Sleep windowTime in the open state30 secondsShould give the service time to recover
Success thresholdSuccesses to close the circuit3 consecutiveHigher = more confidence in the recovery
TimeoutMaximum wait time5 secondsShould be less than the client timeout

Fallback strategies

When the circuit is open, instead of returning a raw error, you can implement fallbacks:

StrategyDescriptionExample
Default valueReturn static dataShow cached catalog
CacheLast successful cached responseProduct price from 5 min ago
Alternative serviceRedirect to a backupUse secondary payment service
Graceful degradationReduced functionalityShow β€œprice not available”
Fail fastImmediate error with a clear messageHTTP 503 with retry-after header

Full flow example

Estado: CERRADO
  Llamada 1 β†’ Servicio B β†’ βœ… OK
  Llamada 2 β†’ Servicio B β†’ βœ… OK
  Llamada 3 β†’ Servicio B β†’ ❌ Timeout
  Llamada 4 β†’ Servicio B β†’ ❌ Error 500
  Llamada 5 β†’ Servicio B β†’ ❌ Timeout
  β†’ Tasa de fallos: 60% > umbral 50%
  β†’ Cambio a ABIERTO ⚑

Estado: ABIERTO
  Llamada 6 β†’ ⚑ Fallback inmediato (no llega a B)
  Llamada 7 β†’ ⚑ Fallback inmediato
  ... (30 segundos de espera)
  β†’ Cambio a SEMI-ABIERTO

Estado: SEMI-ABIERTO
  Llamada 8 β†’ Servicio B β†’ βœ… OK (prueba)
  Llamada 9 β†’ Servicio B β†’ βœ… OK (prueba)
  Llamada 10 β†’ Servicio B β†’ βœ… OK (prueba)
  β†’ 3 Γ©xitos consecutivos
  β†’ Cambio a CERRADO βœ…

Practical example: Circuit Breaker in a payment service

Imagine an order service that calls an external payment provider:

// PseudocΓ³digo de configuraciΓ³n del Circuit Breaker
circuitBreaker = new CircuitBreaker({
  name: "payment-provider",
  failureThreshold: 50,        // 50% de fallos
  requestVolume: 10,           // mΓ­nimo 10 requests para evaluar
  sleepWindow: 30000,          // 30 segundos en estado abierto
  successThreshold: 3,         // 3 Γ©xitos para cerrar
  timeout: 5000,               // 5 segundos de timeout por llamada
  fallback: () => ({
    status: "PENDING",
    message: "Pago en cola, se procesarΓ‘ cuando el servicio se recupere"
  })
});

// Uso
async function procesarPago(pedidoId, monto) {
  try {
    resultado = await circuitBreaker.execute(() =>
      paymentProvider.charge(pedidoId, monto)
    );
    return resultado;
  } catch (error) {
    if (error instanceof CircuitOpenError) {
      // El circuito estΓ‘ abierto, se ejecutΓ³ el fallback
      encolarPagoParaReintento(pedidoId, monto);
      return error.fallbackResult;
    }
    throw error;
  }
}

Monitoring the Circuit Breaker

Circuit state changes are valuable signals for the operations team:

// MΓ©tricas a exponer
circuit_breaker_state{name="payment-provider"} = "closed" | "open" | "half_open"
circuit_breaker_calls_total{name="payment-provider", result="success"}
circuit_breaker_calls_total{name="payment-provider", result="failure"}
circuit_breaker_calls_total{name="payment-provider", result="rejected"}
circuit_breaker_state_transitions_total{name="payment-provider", from="closed", to="open"}

// Alertas recomendadas
ALERT CircuitBreakerOpen
  IF circuit_breaker_state == "open"
  FOR 1m
  LABELS { severity = "warning" }
  ANNOTATIONS { summary = "Circuit breaker abierto para payment-provider" }

Advantages

  • Prevents cascading failures: A downed service does not drag the rest of the system down
  • Fail fast: Requests fail immediately instead of waiting for long timeouts
  • Resource release: No threads or connections are wasted on calls that are going to fail
  • Automatic recovery: The half-open state detects when the service recovers
  • Improved user experience: Fallbacks offer degraded responses instead of errors
  • Observability: State changes are clear signals for alerting and monitoring

Trade-offs / Disadvantages

  • Delicate configuration: Incorrect thresholds can open the circuit too early or too late
  • Complexity in fallbacks: Designing useful fallbacks for each dependency takes effort
  • Stale data: Cache-based fallbacks may return outdated information
  • Complex testing: Simulating the three states and the transitions requires infrastructure
  • Recovery latency: The sleep window introduces a delay between the actual recovery and its detection
  • Does not fix the root cause: The circuit breaker protects the system, but the downstream service still needs fixing

When to use

  • Calls to external services or microservices that can fail or become slow
  • Systems where a failure in one dependency must not affect overall availability
  • When you need to protect shared resources (thread pools, connection pools)
  • APIs that call third-party services with variable SLAs
  • Systems with high-availability requirements where graceful degradation is preferable
  • When you already have metrics and alerts to monitor the state of the circuits

When to avoid

  • Calls to local resources (local database, files) where the overhead is not justified
  • Operations that must complete no matter what (with no possible fallback alternative)
  • Simple systems with few external dependencies
  • When the additional latency of the proxy is unacceptable
  • Asynchronous calls where you already have queues with retries and dead letter queues
  • Prototypes or MVPs where resilience is not a priority

Common technologies and implementations

CategoryOptions
LibrariesResilience4j (Java), Polly (.NET), Hystrix (Java, legacy), opossum (Node.js)
Service MeshIstio, Linkerd, Envoy (circuit breaking at the infrastructure level)
CloudAWS App Mesh, Azure Traffic Manager
MonitoringPrometheus + Grafana, Datadog, New Relic
Complementary patternsRetry with backoff, Bulkhead, Timeout, Rate Limiting

Relationship with other patterns

The Circuit Breaker is rarely used on its own. It works best in combination with other resilience patterns:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Cadena de resiliencia         β”‚
β”‚                                         β”‚
β”‚  Solicitud                              β”‚
β”‚     β”‚                                   β”‚
β”‚     β–Ό                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           β”‚
β”‚  β”‚ Timeout  β”‚  Limita tiempo de espera  β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚       β–Ό                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           β”‚
β”‚  β”‚  Retry   β”‚  Reintenta con backoff    β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚       β–Ό                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           β”‚
β”‚  β”‚ Circuit  β”‚  Corta si hay muchos      β”‚
β”‚  β”‚ Breaker  β”‚  fallos consecutivos      β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚       β–Ό                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           β”‚
β”‚  β”‚ Bulkhead β”‚  AΓ­sla recursos por       β”‚
β”‚  β”‚          β”‚  dependencia              β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚       β–Ό                                 β”‚
β”‚  Servicio downstream                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Timeout Pattern: Defines how long to wait before considering a call as failed. The timeout feeds the circuit breaker with failure data
  • Retry Pattern: Retries failed calls with exponential backoff. Retries happen before the circuit opens; once open, no retry occurs
  • Bulkhead: Isolates resources (threads, connections) per dependency so that a slow service does not consume all the system’s resources
  • Saga Pattern: When a saga step fails and the circuit breaker is open, the saga triggers compensations instead of retrying indefinitely
  • API Gateway: The gateway can implement circuit breakers for each backend service, centralizing the protection
  • Idempotency Pattern: When the circuit closes and operations are retried, idempotency guarantees that effects are not duplicated

Common mistakes

MistakeConsequenceSolution
Threshold too lowCircuit opens on isolated failuresIncrease the minimum request volume
Sleep window too shortCircuit oscillates between open and closedGive more recovery time
No fallback definedRaw error to the user when the circuit opensDesign fallbacks for each dependency
A single global circuit breakerOne slow service affects everyoneOne circuit breaker per dependency
Not monitoring transitionsYou don’t detect problems in timeAlerts on state changes
Ignoring the half-open stateExcessive traffic during testingLimit the test requests

Next steps

The Circuit Breaker is a fundamental piece of resilience in microservices. To round out your understanding of resilience patterns, explore the Retry Pattern for handling transient failures with backoff, the Timeout Pattern for limiting waits, and the Bulkhead Pattern for isolating resources per dependency.