All chapters in this track
- 01Chapter 1: Why System Design Is Its Own Skill
- 02Chapter 2: Core Building Blocks and Vocabulary
- 03Chapter 3: Eliciting Non-Functional Requirements
- 04Chapter 4: Back-of-Envelope Capacity Estimation
- 05Chapter 5: Caching Strategies and Cache Invalidation at the Systems Level
- 06Chapter 6: Load Balancing and Horizontal Scaling
- 07Chapter 7: Database Selection, Replication, and Sharding at a Systems Level
- 08Chapter 8: Asynchronous Processing, Message Queues, and Event-Driven Design
- 09Chapter 9: API Design from a Systems Perspective: REST, gRPC, GraphQL
- 10Chapter 10: The CAP Theorem and Consistency Models in Practical Terms
- 11Chapter 11: Microservices vs. Monoliths and Drawing Service Boundaries
- 12Chapter 12: Designing for Failure: Redundancy, Failover, Graceful Degradation
- 13Chapter 13: A Repeatable Framework for Any System Design Problem
- 14Chapter 14: Worked Example: URL Shortener
- 15Chapter 15: Worked Example: Chat System
- 16Chapter 16: Worked Example: News Feed
- 17Chapter 17: Worked Example: Rate Limiter
Chapter 12 of 17
Chapter 12: Designing for Failure: Redundancy, Failover, Graceful Degradation
Why this matters
Every component covered so far in this book — servers, databases, network links, caches — will fail eventually; this isn't pessimism, it's a statistical certainty at real scale (with enough components running for long enough, individual failures become a "when," not an "if"). Designing for failure explicitly, rather than assuming components stay healthy, is what separates a system that degrades gracefully from one that experiences a complete, cascading outage from a single failed part.
Redundancy: eliminating single points of failure
[BEGINNER] A single point of failure (SPOF) is any component whose failure takes down the entire system — the most fundamental failure-design principle is identifying and eliminating these wherever the cost of doing so is justified by the component's actual criticality.
SPOF example: one database server, one load balancer instance, one
specific server handling a critical function with no
backup.
Redundancy fix: multiple database replicas (Chapter 7) with automatic
failover, multiple load balancer instances (often behind
a DNS-based or cloud-provider-managed redundancy layer),
multiple instances of every critical service (Chapter 6's
horizontal scaling, which ALSO provides redundancy as a
side effect of having multiple instances for capacity reasons).
[SENIOR INSIGHT] Redundancy has a real, deliberate cost (running duplicate infrastructure that mostly sits idle, ready to take over) — this is precisely why identifying SPOFs and eliminating them should be prioritized by actual criticality and probability, not applied uniformly to every component regardless of its actual failure impact. A component whose failure only affects a rarely-used administrative feature doesn't warrant the same redundancy investment as the primary database or the authentication service.
Failover: automatically routing around a failed component
[BEGINNER] Failover is the mechanism that actually uses redundancy when a failure occurs — automatically detecting a failed component and routing traffic/responsibility to a healthy replacement, ideally with minimal or no manual intervention and minimal user-visible disruption.
Database failover: primary fails -> a replica (Chapter 7) is automatically
promoted to primary -> application configuration
updates to point at the new primary -> service resumes,
typically with a brief interruption during the
detection-and-promotion window.
Load balancer / server failover: a health check (Chapter 6) detects a
failed instance -> the load balancer stops routing
traffic to it -> remaining healthy instances absorb
the load -> (separately) a new instance is provisioned
to replace the failed one.
[IN PRACTICE] Failover is never instantaneous — there's always a detection window (how long until the health check notices the failure) and a promotion/rerouting window (how long until traffic actually flows to the replacement) — this real, non-zero gap is exactly why a stated availability target (Chapter 3's "how many nines") needs to account for failover speed specifically, not just redundancy's mere existence: redundant infrastructure that takes ten minutes to fail over doesn't achieve a five-nines availability target on its own, regardless of how many replicas exist.
Graceful degradation: staying partially functional instead of fully failing
[SENIOR INSIGHT] Graceful degradation is designing a system so that when one component fails, the overall system continues operating with reduced functionality, rather than the failure of one part taking down the whole system — directly the companion OOP book's Chapter 14 point about single points of failure in a design, and the companion Coding Standards book's Chapter 5 fail-fast-vs-fail-gracefully distinction, both applied at the whole-system architecture scale.
Without graceful degradation: the recommendation service fails ->
the ENTIRE product page fails to load, because the page's rendering
logic has a hard dependency on recommendations being present.
With graceful degradation: the recommendation service fails -> the
product page loads normally, simply WITHOUT the recommendations
section (or with a cached, slightly-stale version) -- the core
functionality (viewing and buying the product) remains fully available
despite a real, ongoing failure elsewhere in the system.
The design principle this requires: identifying which pieces of functionality are truly core (must work, or the whole request should fail — Chapter 8's synchronous-path test) versus genuinely optional enhancements (should degrade gracefully if unavailable) — and architecting each dependency's failure handling according to that classification, explicitly, rather than treating every dependency as equally critical by default.
The circuit breaker: preventing a failing dependency from cascading
[IN PRACTICE] Directly the companion Networking book's Chapter 11 pattern, applied at the systems-design level: a circuit breaker stops sending requests to a dependency that's already failing, for a cooldown period, rather than continuing to send requests (and continuing to consume resources waiting for them to time out) to something that's clearly not going to respond successfully — this prevents one failing, slow dependency from consuming so many resources (threads, connections) in the calling service that the calling service itself becomes unresponsive, turning one component's failure into a cascading failure across the whole system.
Common mistakes
- Not identifying single points of failure explicitly during design, discovering them only when one actually fails in production and takes down more of the system than necessary.
- Applying uniform redundancy investment regardless of a component's actual criticality, over-investing in redundancy for low-impact components while potentially under-investing in genuinely critical ones.
- Treating every dependency as equally critical, missing the graceful-degradation opportunity where a non-core dependency's failure could be isolated rather than taking down an entire user-facing flow.
- Not accounting for failover's real detection-and-promotion time gap when stating an availability target — redundancy alone doesn't achieve a specific "nines" target; the speed of failover matters just as much.
Discussion / interview questions
-
"What is a single point of failure, and how would you identify and address one in a system you're designing?" Strong answer: Any component whose failure takes down the entire system; addressed via redundancy (multiple instances/replicas) and automatic failover — prioritized by the component's actual criticality and failure probability, not applied uniformly regardless of impact.
-
"Why isn't redundancy alone sufficient to guarantee high availability?" Strong answer: Failover isn't instantaneous — there's a real detection window (noticing the failure) and a promotion/rerouting window (redirecting traffic to the replacement); an availability target needs to account for this real gap, not just the mere existence of redundant infrastructure.
-
"What is graceful degradation, and how would you design for it in a system with an optional recommendation feature?" Strong answer: Designing so a non-core dependency's failure doesn't take down the whole request — for a recommendation feature specifically, the product page should still load and function fully (viewing, buying) even if recommendations are unavailable, simply omitting or showing a cached version of that one section instead of failing the entire page load.
-
"What is a circuit breaker, and what specific failure mode does it prevent?" Strong answer: Stops sending requests to an already-failing dependency for a cooldown period rather than continuing to send requests that will likely fail/time out — preventing the calling service from exhausting its own resources (threads, connections) waiting on a dependency that isn't going to respond, which would otherwise turn one component's failure into a cascading failure across the calling service too.
Chapter summary / cheat sheet
- Identify and eliminate single points of failure via redundancy, prioritized by actual criticality — not applied uniformly regardless of impact.
- Failover (automatically routing around a failed component) has a real, non-zero detection-and-promotion time gap that must be accounted for when stating an availability target.
- Graceful degradation lets a system stay partially functional when a non-core dependency fails, rather than the failure taking down an entire request — requires explicitly classifying dependencies as core versus optional.
- A circuit breaker prevents a failing dependency from cascading into the calling service's own failure, by stopping wasted requests to something that's clearly not going to respond successfully.