Resilience Patterns: Circuit Breakers, Retries, Bulkheads
Resilience patterns are design techniques that allow distributed systems to continue functioning when individual components fail or degrade. Circuit breakers prevent cascading failures by stopping requests to a failing dependency before the failure spreads. Retries with exponential backoff recover from transient failures without creating retry storms. Bulkheads isolate failures to a subset of resources so that one failing dependency cannot consume all available capacity and degrade unrelated functionality. Together they form the baseline resilience architecture for services that depend on external APIs, databases, and other network-connected components.
Written by Yashveer Singh, founder of Yashveer Labs.
What you need to know
- Retries with exponential backoff and jitter handle transient failures. Circuit breakers handle sustained failures. Both are needed for robust distributed system behavior.
- Without circuit breakers, a failing downstream service drains threads, exhausts connection pools, and cascades failures to the calling service. The calling service goes down because of a dependency it does not own.
- Bulkheads limit the blast radius of a dependency failure to the specific functionality that depends on that service.
- Timeouts on every external call are the prerequisite for all other resilience patterns. Without timeouts, retries never give up and circuit breakers never open.
- Use a library for resilience patterns. Manual implementations miss edge cases that battle-tested libraries handle correctly.
The core argument
A web service that calls five external dependencies inherits the failure probability of all five. If each dependency has 99.9% availability (one 9-minute outage per week), the calling service's availability without resilience patterns is 0.999^5 = 99.5%, or about 21 hours of downtime per year attributable entirely to dependency failures. With resilience patterns, the calling service can stay up during most dependency failures by degrading gracefully: returning cached data when the cache is unavailable, showing a fallback message when a non-critical service is down, and queuing operations that require the failed service for replay when it recovers.
The implementation order matters. Start with timeouts on every external call. A service that calls a database without a timeout will hang indefinitely on a connection that will never complete, blocking that thread forever. Timeouts are the foundation on which retries and circuit breakers operate. Add retries with exponential backoff for idempotent operations (GET requests, reads). Add circuit breakers for high-traffic paths to dependencies that experience periodic degradation. Add bulkheads when a single dependency's failure is consuming disproportionate resources and affecting unrelated functionality.
The pattern combination that matters for SaaS applications is: short timeouts (500ms to 2 seconds depending on the operation), two to three retries with exponential backoff and jitter for transient failures, circuit breakers that open after 50% failure rate over 10 seconds and half-open after 30 seconds to test recovery, and bulkheads that limit concurrent calls to any single external dependency to a configured maximum. This combination handles the common failure modes that real distributed systems encounter.
Common mistakes
- Not setting timeouts on database connections and external API calls. Calls without timeouts block indefinitely on network failures, connection timeouts, and hanging servers. Every external call needs a timeout configured at the client level. The default timeout for most HTTP clients is either very long or infinite; always set explicit timeouts.
- Retrying non-idempotent operations. A POST request that creates a new record should not be retried automatically if the first attempt succeeds but the response is lost due to a network error. Retrying creates a duplicate record. Only idempotent operations (GET, DELETE with idempotency keys, POST with idempotency keys) should be retried automatically. Non-idempotent operations should fail fast and let the caller decide whether to retry.
- Not adding jitter to exponential backoff. Retries without jitter from many concurrent clients create synchronized retry waves that hit the recovering service simultaneously. Add random jitter: wait = baseDelay * 2^attempt + random(0, 100ms). This spreads retries across the backoff window and reduces load on a recovering service.
- Opening circuit breakers on percentage-based thresholds with too small a sample window. A circuit breaker that opens when 50% of requests fail in a 5-second window will open immediately if 2 of the first 4 requests fail in a new deployment. Set minimum request count thresholds (do not open the circuit breaker until at least 20 requests have been evaluated in the window) to prevent premature circuit opening from small samples.
- Not implementing fallback behavior when the circuit is open. An open circuit breaker that returns a 500 error is better than an open circuit that blocks indefinitely, but it is worse than an open circuit that returns a cached result, a degraded response, or a graceful "service temporarily unavailable" message. Define the fallback behavior for each circuit breaker when implementing it.
Where to start
- Audit every external dependency call for timeout configuration. Check every HTTP client, database connection pool, and cache client configuration for explicit timeout settings. Add timeout configuration to any call that does not have one. For API calls: 500ms to 2 seconds. For database queries: 5 to 30 seconds depending on operation type. For long-running operations: longer timeouts with progress tracking.
- Install a resilience library and add retry logic to the highest-impact external calls. For Node.js: cockatiel. For Python: tenacity. Configure retry for GET requests and other idempotent operations on the two or three external services that experience the most transient failures. Add exponential backoff with jitter to the retry configuration.
- Add circuit breakers to the calls that, when they fail, would cascade the failure to user-facing functionality. The payment processor integration, the primary database connection, and any external API called on every user request are the highest-priority circuit breaker candidates. Start with conservative circuit breaker thresholds (70% failure rate over 10 seconds) and tighten based on observed behavior.
Related reading
Frequently asked
The person who wrote this
Yashveer Singh wrote this. Class 12, Commerce track, full stack developer. The categories do not align, which is the point. The work runs in production. Everything else is paperwork. If the project on your plate is the one this article describes, you can reach me through the contact page or through Instagram. I will read it. I will reply. That is the standard.
Posts that line up with this one.
- Backend, APIs, and System Design
Idempotency Keys: A Pattern Every Senior Engineer Should Master
Idempotency keys are a small implementation with an outsized impact on system reliability. Here is the pattern, the edge cases, and the production pitfalls that most introductions skip.
- Backend, APIs, and System Design
JSON Columns in Postgres: When They Make Sense
JSON columns in Postgres are genuinely useful for flexible, semi-structured data. They are also frequently misused as a shortcut to avoid schema design. Here is when to use them and when to use normalized tables instead.
- Backend, APIs, and System Design
Kafka in 2026: When You Need It and When You Do Not
Kafka is powerful, but most startups reach for it before they need it. Here is how to decide.
- Backend, APIs, and System Design
Lambda Cold Starts: Why They Still Matter in 2026
Cold starts have improved significantly but have not been eliminated. Here is the current state of cold start latency, which use cases still require mitigation, and the practical patterns that keep them from affecting users.