Circuit Breaker Patterns: Failing Fast Without Failing Hard
How circuit breakers prevent cascading failures in distributed systems, with practical implementation patterns.
February 21, 2026 · 8 min · 1535 words · Rob Washington
Table of Contents
Your payment service is down. Every request to it times out after 30 seconds. You have 100 requests per second hitting that endpoint. Do the math: within a minute, you’ve got 6,000 threads waiting on a dead service, and your entire application is choking.
Borrowed from electrical engineering, the circuit breaker pattern detects failures and prevents the system from repeatedly trying operations that are likely to fail.
enumCircuitState{CLOSED='closed',OPEN='open',HALF_OPEN='half_open'}interfaceCircuitBreakerOptions{failureThreshold: number;// Failures before opening
successThreshold: number;// Successes to close from half-open
timeout: number;// Milliseconds before trying half-open
}classCircuitBreaker{privatestate: CircuitState=CircuitState.CLOSED;privatefailures: number=0;privatesuccesses: number=0;privatelastFailureTime: number=0;constructor(privateoptions: CircuitBreakerOptions){}asyncexecute<T>(fn:()=>Promise<T>):Promise<T>{if(this.state===CircuitState.OPEN){if(Date.now()-this.lastFailureTime>this.options.timeout){this.state=CircuitState.HALF_OPEN;this.successes=0;}else{thrownewError('Circuit breaker is open');}}try{constresult=awaitfn();this.onSuccess();returnresult;}catch(error){this.onFailure();throwerror;}}privateonSuccess():void{this.failures=0;if(this.state===CircuitState.HALF_OPEN){this.successes++;if(this.successes>=this.options.successThreshold){this.state=CircuitState.CLOSED;}}}privateonFailure():void{this.failures++;this.lastFailureTime=Date.now();if(this.state===CircuitState.HALF_OPEN||this.failures>=this.options.failureThreshold){this.state=CircuitState.OPEN;}}}
The defaults matter. Here’s what works in production:
1
2
3
4
5
6
7
8
9
10
11
12
constpaymentCircuit=newCircuitBreaker({failureThreshold: 5,// Trip after 5 consecutive failures
successThreshold: 3,// Need 3 successes to close
timeout: 30000// Try again after 30 seconds
});// Usage
asyncfunctionprocessPayment(order: Order):Promise<PaymentResult>{returnpaymentCircuit.execute(async()=>{returnawaitpaymentService.charge(order);});}
constpools={payments: newConnectionPool({maxSize: 20}),notifications: newConnectionPool({maxSize: 10}),analytics: newConnectionPool({maxSize: 5})};// Each service gets its own pool
// If analytics is slow, payments still has full capacity
Combine both patterns: circuit breakers per service, connection pools per service. Failures stay contained.
Fail fast: Open circuits reject requests immediately instead of waiting for timeouts
Fail gracefully: Always have a fallback strategy
Tune for your service: Payment systems and analytics have different tolerance
Observe everything: You can’t fix what you can’t see
Combine with bulkheads: Isolation and protection work better together
Circuit breakers turn catastrophic failures into graceful degradation. Your users get “temporarily unavailable” instead of “entire site is down.” That’s the difference between an incident and a blip.
Your system will fail. The question is whether the failure spreads or stays contained.
📬 Get the Newsletter
Weekly insights on DevOps, automation, and CLI mastery. No spam, unsubscribe anytime.