Fault Tolerance, High Availability, and Disaster Recovery Questions
Keeping a system serving despite failure, from code-level resilience to infrastructure-level recovery: circuit breakers, retries with backoff and jitter, timeouts, bulkheads, graceful degradation, and preventing cascading failures, alongside redundancy, failover (active-active versus active-passive), RPO and RTO objectives, backup and restore, and multi-region failover. Covers dependency-failure isolation, chaos engineering to validate resilience, failure-mode analysis, designing to nines of availability, cost-versus-availability tradeoffs, and recovery runbooks. Spans both the patterns that isolate partial failure and the disaster-recovery planning that restores a business-critical system after a major outage.
What's the difference between a backup and replication for disaster recovery? When would you rely on a backup-based restore instead of cross-region replication, and why might you need both?
Sample Answer
A backup is a point-in-time copy, taken on a schedule and stored separately from the live system, that you restore from after something goes wrong. Replication is a continuously (or near-continuously) updated copy of the current state, kept on standby to take over as the live system. The distinction that matters most in practice: replication faithfully copies whatever the primary does, including its mistakes, while a backup gives you a version of the data from before the mistake happened.
Comparing the two
| Dimension | Backup | Replication |
|---|---|---|
| What it protects against | Logical errors: bad deploy, accidental deletes, corruption, ransomware | Infrastructure failure: node crash, AZ/region outage |
| Recovery speed | Slower: restore process has to run before service resumes | Fast: replica can often be promoted directly |
| Data freshness at recovery | As of the last backup (minutes to hours old, depending on cadence) | As of the last replicated write (near-real-time) |
| Protects against corruption? | Yes, by design (an earlier snapshot predates the corruption) | No, corruption on the primary replicates to the standby just as fast as any other write |
| Retention | Cheap to keep for weeks/years (cold storage) | Effectively none; it's a live mirror of "now," not a history |
When to rely on backup-based restore instead of replication
Reach for a backup restore when the failure is a logical one: someone ran a bad migration, a bug silently corrupted rows, or ransomware encrypted the data. Replication doesn't help here, and can actively hurt, because it will faithfully copy the corrupted or encrypted state to the replica just as reliably as it copies good writes. This is also why backups need to be immutable or air-gapped (physically or logically disconnected from any network the production environment can reach, so a compromised production system has no path to alter or delete them), not just "another copy": if a backup is reachable and mutable from the same compromised environment, it's not meaningfully protecting against the ransomware scenario it exists for.
Worked example: why backup-only can't meet a tight recovery target
Take a 500 GB primary database and a realistic restore throughput of 200 MB/s (both pinned as inputs to this estimate, not a measured benchmark of any specific system):
restore time=200 MB/s500×1024 MB=2560 seconds≈42.7 minutesThat's before accounting for the time to detect the failure and kick off the restore at all. If the recovery requirement is anywhere near a 5-10 minute RPO/RTO, backup-and-restore alone structurally cannot meet it at this data size and throughput, no matter how good the runbook is; the bottleneck is physical (bytes per second), not procedural. This is the concrete reason cross-region replication exists alongside backups rather than instead of them: replication gets the speed (a replica is already there, ready to promote), backups get the safety net (a clean point to roll back to when the live data itself, replicated or not, turns out to be wrong).
Trade-offs and pitfalls
The common mistake is treating replication as a backup substitute because it "keeps a copy of the data": it does, but that copy has zero lag on propagating mistakes, which is exactly the property a backup needs to not have. The pattern that actually works combines both: replication (in-region synchronous, cross-region async) for fast failover and a tight RPO on infrastructure failures, plus regular immutable, tested backups for the logical-error and ransomware case replication can't cover. "Tested" is doing real work in that sentence too: a backup nobody has restored from recently is a backup whose actual restore time and integrity are both unverified, and 42.7-minute math on paper is worthless if the real restore process has never been run end-to-end.
What's the difference between fault tolerance, high availability, and resilience? Give a concrete example of each, and explain how they show up in operational metrics like MTTR and MTBF.
Sample Answer
Fault tolerance, high availability, and resilience are related but answer different questions. Fault tolerance means a failure is masked entirely: the system keeps producing correct output with no visible interruption. High availability (HA) means downtime is minimized and recovery is fast, but a brief, bounded interruption is expected and acceptable. Resilience is the broadest term: the system's ability to keep delivering acceptable service under any kind of stress, not just component failure, including load spikes, bad inputs, or a slow dependency, usually by degrading gracefully rather than failing outright.
How each shows up architecturally
| Property | What it guarantees | Typical mechanism | Concrete example |
|---|---|---|---|
| Fault tolerance | No visible interruption during a failure | Redundant, synchronized components that vote or replicate in lockstep (RAID, dual power supplies, Raft/Paxos-replicated state) | A 3-node Raft cluster loses one node; the other two still form a quorum (a majority of the cluster, here 2 of the original 3 nodes, enough to safely keep operating and elect a leader if needed) and serve every request with zero downtime |
| High availability | Short, bounded interruption, fast automated recovery | Health-checked redundancy plus automated failover (active-passive DB failover, load-balanced app tier) | A primary database instance crashes; a monitor detects it in seconds and promotes a replica, so the outage is measured in seconds to low minutes, not zero |
| Resilience | Acceptable service continues even when something can't be masked or failed over cleanly | Circuit breakers, timeouts, bulkheads, graceful degradation, autoscaling | A recommendation service starts timing out; the product page serves without recommendations instead of failing the whole page load |
Fault tolerance and HA are usually about infrastructure failing; resilience is about the system's response to any kind of stress, including ones where nothing has technically "failed" yet (a slow but technically-up dependency, for instance).
Effect on MTTR and MTBF
MTTR (mean time to repair/recover) and MTBF (mean time between failures) combine into steady-state availability:
A=MTBF+MTTRMTBFFault tolerance mainly extends effective MTBF from the user's point of view: individual component failures still happen at whatever rate they happen, but they don't count as user-visible failures because they're masked, so the failure interval a customer would notice grows. High availability mainly drives MTTR down: the goal isn't to prevent the primary from ever failing, it's to make detection and recovery fast and automatic. Resilience patterns move both numbers in the same direction from a different angle: a circuit breaker doesn't prevent a dependency from failing (MTBF of the dependency is unchanged) but it prevents that dependency's failure from becoming your incident at all, which is a third way to improve the user-visible number.
Worked example
Take a service with a component MTBF of 720 hours and an MTTR of 30 minutes (0.5 hours) once a failure is detected and recovered:
A=720+0.5720=720.5720≈0.99931(99.931%)That converts to annual downtime using 525,600 minutes per year (365 days × 24 hours × 60 minutes):
(1−0.99931)×525,600≈364.8 minutes/year≈6.08 hours/yearNow compare two improvements starting from that baseline, holding the other variable fixed:
- Cut MTTR to 5 minutes (better HA: faster automated failover) with MTBF unchanged at 720h: A=720/720.083≈0.999884, about 61.0 minutes/year of downtime, a ~6x reduction driven entirely by faster recovery.
- Double MTBF to 1440 hours (better fault tolerance: the failure that used to happen now gets masked half as often) with MTTR unchanged at 30 min: A=1440/1440.5≈0.999653, about 182.4 minutes/year, a 2x reduction.
Neither number is "the metrics." They're two independent levers on the same availability formula, and which one is cheaper to pull depends on the system: automating failover (MTTR) is often cheaper than adding redundant hardware paths everywhere (MTBF).
Trade-offs and pitfalls
The common mix-up in interviews is treating "high availability" as if it means "never goes down," which is what fault tolerance actually promises, and at a much higher engineering cost (consensus protocols, lockstep replication) than HA's health-check-and-failover pattern. A resilient system is not automatically fault-tolerant or highly available either: a service with excellent circuit breakers and graceful degradation for its dependencies can still have a single database with no HA story of its own. These three properties are complementary, not substitutes, and a system typically needs different amounts of each depending on the blast radius of the component: mask failures (fault tolerance) for the smallest, cheapest, most critical primitives; fail over fast (HA) for stateful tiers where full masking is expensive; and degrade gracefully (resilience) at the edges where "reduced functionality" beats "hard failure." The same reasoning applies outside a classic web-service stack too: a GPU training job gets fault tolerance from checkpointing plus redundant nodes (a crashed worker resumes from the last checkpoint instead of restarting the whole job), and a data pipeline gets resilience from feature-store (a system that serves precomputed inputs to a machine-learning model) fallback values or a stale-but-served cache when an upstream API is delayed rather than hard-failing the request.
Define cascading failure and walk through a realistic example: service C fails, B (which depends on C) gets overloaded, and A (which depends on B) starts degrading too. At each layer, what protection would you put in place to stop the cascade from propagating?
Sample Answer
Direct answer
A cascading failure is when one component's failure increases load or latency on the components that depend on it, and that increased load causes those components to fail too, propagating outward until a large part of the system is affected, even though only one component actually broke in the first place. The mechanism is almost always resource exhaustion: threads, connections, or memory tied up waiting on the failed component instead of being freed quickly.
Walkthrough: C fails, B overloads, A degrades
flowchart LR
A[API Gateway] -->|rate limit and timeout| B[Order Service]
B -->|bulkhead pool: payments| C[Payment Service]
C -.fails.-> B
B -->|circuit breaker opens| D[Fallback: queue order for async retry]
A -->|circuit breaker opens| E[Fallback: 503 with Retry-After]
B -->|isolated pool: other deps unaffected| F[Inventory Service]
- C (Payment Service) fails, hanging instead of returning errors quickly, perhaps due to a downstream outage of its own.
- B (Order Service) calls C without a tight timeout. Each call to C now blocks for far longer than normal, tying up a thread or connection from B's pool for the duration.
- B's resource pool exhausts. As more requests arrive at B, more threads get stuck waiting on C, until B has no capacity left to serve any request, including ones that don't even touch C.
- A (API Gateway) calls B, and B is now slow or unresponsive for everything, so A's calls to B start timing out or queueing too, degrading A's own capacity in turn.
Worked example: how fast does B's pool actually exhaust?
Little's Law relates the number of requests in flight to the arrival rate and the time each spends being processed:
L=λWSay B receives 500 requests per second, and under normal conditions each call to C takes 50ms:
Lnormal=500×0.05=25 concurrent in-flight requests25 concurrent requests is a light load on a typical connection pool. Now C hangs, and B's HTTP client has no explicit timeout of its own, falling back to a default of 30 seconds:
Lfailure=500×30=15,000 concurrent in-flight requests neededIf B's thread pool has 200 threads, the time to exhaust it entirely is:
texhaust=500200=0.4 sUnder 400 milliseconds. That's how quickly a single hung dependency with no timeout turns into total unavailability for a service handling 500 requests per second: the pool never gets close to steady-state at the 30-second hang time, it simply fills with stuck requests almost instantly and stays full.
Protections at each layer
- At B, calling C: a tight, explicit timeout (measured in low hundreds of milliseconds, not the client library's 30-second default) so a hung call fails fast and frees the thread quickly; a circuit breaker that opens after a run of failures or timeouts, so B stops even attempting calls to C once it's clearly down, and falls back to queueing the order for later processing; a bulkhead, a dedicated connection pool just for calls to C, so exhaustion from C-related calls doesn't consume the threads B needs to serve requests that don't touch C at all (like inventory checks).
- At A, calling B: the same pattern one layer up, a timeout on calls to B, a circuit breaker that trips once B's error rate or latency crosses a threshold, and a fallback (a fast 503 with
Retry-Afterrather than a hung request) so A's own capacity isn't consumed waiting on a B that's already struggling.
Trade-offs & pitfalls
Timeouts that are too aggressive cause false-positive failures under normal, brief latency variance; timeouts that are too loose don't prevent the cascade fast enough, as the Little's Law example shows. Bulkheads cost real resources (a dedicated pool per dependency uses more total connections or threads than one shared pool) in exchange for isolation, so they're worth applying to the dependencies most likely to fail or most likely to take down unrelated traffic if they do. The most common mistake is only protecting the first hop (B to C) and assuming that's sufficient; as the walkthrough shows, without protection at the A to B hop too, the failure still reaches A once B is degraded, just one layer later.
What's the difference between Recovery Time Objective and Recovery Point Objective? Given the business requirement 'payments must be restored within 30 minutes with no more than 5 minutes of data loss,' walk through how that translates into your replication and backup design.
Sample Answer
RTO (Recovery Time Objective) is how long you're allowed to be down: the maximum acceptable gap between an outage starting and service being restored. RPO (Recovery Point Objective) is how much data you're allowed to lose: the maximum acceptable gap, measured in time, between the last durably captured write and the moment of failure. The requirement "payments must be restored within 30 minutes with no more than 5 minutes of data loss" is literally RTO = 30 min and RPO = 5 min stated in plain language, and each number drives a different part of the design.
What each number drives
RPO = 5 minutes drives replication and backup frequency. A nightly or even hourly backup can't meet this: if the outage happens 4 hours after the last backup, you'd lose 4 hours of transactions, not 5 minutes. A 5-minute RPO effectively requires continuous replication (near-synchronous in-region, or streaming WAL (write-ahead log: a durable, ordered record of every change, written before it's considered applied) or CDC (change-data-capture: a stream of those same row-level changes read off that log) shipping to the DR site) with replication lag actively monitored and alarmed well below the 5-minute budget, plus point-in-time recovery for protection against logical corruption that replication alone would just copy.
RTO = 30 minutes drives standby readiness and failover automation. A cold-standby DR site that has to be provisioned from scratch after the fact will blow past 30 minutes just on infrastructure boot time. A 30-minute RTO points toward a warm standby (already running, sized down, kept current via the same replication that satisfies the RPO) with an automated failover runbook: health-check detection, automated promotion, and DNS/routing cutover, because a manual, human-paged process realistically eats 10-15 minutes just in detection and decision-making before any recovery action starts.
Worked example: what these numbers cost against an annual SLA
A useful way to make the 30-minute number concrete is to check it against annual downtime budgets at standard availability tiers, using 525,600 minutes per year (365 × 24 × 60):
| Availability tier | Allowed downtime/year |
|---|---|
| 99.9% ("three nines") | 525,600×0.001=525.6 min ≈8.76 hours |
| 99.99% ("four nines") | 525,600×0.0001=52.56 min |
| 99.999% ("five nines") | 525,600×0.00001=5.256 min |
A single incident with a 30-minute RTO, if the service is held to a 99.99% SLA, consumes:
52.5630≈0.571(57.1%)of the entire year's downtime budget in one event. That reframes "30 minutes sounds generous" into "this design can absorb roughly one such incident a year and still hit four nines," which is exactly the kind of number that should drive whether the DR design gets warm-standby automation now or gets revisited after the first real incident eats most of the annual budget.
Trade-offs and pitfalls
The most common mix-up is treating RTO and RPO as interchangeable "how bad was it" numbers instead of two independent design constraints: a system can have a great RTO (back up in 2 minutes) and a terrible RPO (lost the last hour of writes) if it fails over to a backup instead of a live replica, or the reverse (RPO≈0 via synchronous replication, but a slow, manual promotion process blows the RTO). Both have to be solved, and usually by different mechanisms: RPO is a replication/backup-cadence problem, RTO is an automation/standby-readiness problem. A second pitfall specific to payments: RPO=0 sounds like the obviously "safer" number to chase, but strict synchronous replication that blocks writes during a replica outage can turn a replication hiccup into an availability incident, trading a data-loss risk you might never hit for a downtime risk you're now taking on every day.
What's the bulkhead pattern, and how does it stop one failing dependency or noisy tenant from taking down the whole system? Give a concrete example of where you'd draw the isolation boundary.
Sample Answer
Direct answer
The bulkhead pattern partitions a system's resources (thread pools, connection pools, CPU, or entire nodes) into isolated compartments, named after a ship's watertight bulkheads, so that one failing dependency or one noisy tenant can only exhaust the resources in its own compartment, not the resources every other caller depends on. Without bulkheads, a single slow or misbehaving dependency can consume every available thread or connection in a shared pool, and a completely healthy code path fails simply because it couldn't get a thread to run on.
Where to draw the isolation boundary
A concrete example: an API gateway calls three downstream services, an inventory service, a recommendations service, and a payments service, all through one shared thread pool. If recommendations starts responding slowly, every thread in the shared pool eventually ends up blocked waiting on recommendations calls, and inventory and payment requests start timing out too, even though nothing is wrong with either of them. The fix is a dedicated, bounded thread pool (or connection pool) per downstream dependency: recommendations gets its own pool of, say, 10 threads, so a recommendations outage can stall at most those 10 threads and its own queue, while inventory and payments keep running normally on their own separate pools.
The boundary should sit wherever one caller's failure or slowness shouldn't be able to spill onto another caller's request. Common places to draw it:
- Per-downstream-dependency, as in the example above: each external service or database gets its own pool so a slow one can't starve calls to a fast one.
- Per-tenant, in a multi-tenant system: each tenant (or tenant tier) gets a capped share of connections or CPU so one noisy or abusive tenant can't degrade service for everyone else on shared infrastructure.
- Per-criticality-tier: payment and auth paths get reserved capacity separate from lower-priority paths like analytics or notifications, so a spike in low-priority traffic can't crowd out the paths that actually matter.
Trade-offs & pitfalls
Bulkheads trade utilization for isolation: reserved capacity that a compartment isn't currently using sits idle rather than being available to a busier compartment, so a poorly sized bulkhead can cause localized throttling even while the system as a whole has spare capacity. Sizing is the actual hard part in practice, not the pattern itself: too small and a legitimate burst of normal traffic gets rejected by its own bulkhead; too large and the isolation becomes theoretical, because if every pool is sized close to the shared pool's original total, a single compartment can still consume enough of the machine's real resources (CPU, memory, file descriptors) to degrade its neighbors even though the pool counters look fine. Bulkheads are also a different tool from a circuit breaker and the two are frequently confused: a bulkhead limits how much of a shared resource one dependency can consume (a capacity boundary), while a circuit breaker stops sending requests to a dependency once it's clearly failing (a decision to stop calling at all); they're complementary, since the bulkhead caps the damage while the circuit breaker is deciding whether to keep trying, and production systems typically use both on the same dependency together. The same reasoning extends beyond web request threads: an ML-serving platform running GPU inference for multiple models on shared hardware applies the identical idea by pinning each model (or tenant) to a dedicated slice of GPU memory and compute, so one model that starts issuing runaway-batch-size requests can't starve GPU capacity away from every other model sharing that hardware.
Unlock Full Question Bank
Get access to all 16 Fault Tolerance, High Availability, and Disaster Recovery interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.