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 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.
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.
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 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 N+1 and N+2 redundancy? For a service normally sized at 10 instances, walk through what each strategy actually buys you in failure tolerance, and when the extra cost of N+2 is worth it.
Sample Answer
Direct answer: N+1 means you provision one spare unit beyond what's needed to serve current load, so the system tolerates exactly one simultaneous failure with zero capacity loss. N+2 provisions two spares, tolerating two simultaneous failures (or one failure plus a second one arriving while the first is still being repaired). For a service sized at 10 instances, N+1 is 11 instances and N+2 is 12; the extra instance in N+2 is worth it when failures are likely to be correlated or when repair (MTTR) is slow enough that a second failure landing during the first one's recovery window is a real possibility, not a hypothetical.
Structured elaboration
- What "N" means: N is the number of units actually required to serve load at your target performance, not the number you happen to run. If 10 instances are needed to handle peak traffic at acceptable latency, N=10.
- N+1: one extra unit. Any single instance, host, rack, or power supply can fail and the system still serves at full capacity from the remaining N. It does not protect against a second, overlapping failure.
- N+2: two extra units. Protects against two simultaneous failures, which matters specifically during the repair window of the first failure (you're running on N+1 capacity while node 1 is being replaced; if node 2 fails during that window, N+1 would drop you below N, but N+2 still covers you).
- When N+2's extra cost is worth it: the decision comes down to how correlated failures are and how long repair takes, not just how critical the service is in the abstract.
Worked example: quantifying the risk N+2 removes
A capacity shortfall only happens when multiple instances are down at the same time, which means the model has to use the instantaneous probability that an instance is down at any given moment, not the probability that it fails at some point during the year (an annual failure probability answers a different question and silently ignores repair-window overlap). The right building block is the instantaneous-unavailability formula: at any random moment, the fraction of time a single instance has historically spent broken and being repaired is MTTR divided by the full working-plus-repair cycle, MTBF+MTTR, which is exactly the probability that instance happens to be down at an arbitrary moment in time:
q=MTBF+MTTRMTTRPin illustrative values: each instance has an MTBF of 8,760 hours (fails on average about once a year) and an MTTR of 4 hours (time to detect and replace or restart a failed instance). Then:
q=8760+44=87644≈0.000456(0.0456%)That's the probability any single instance is down (mid-repair) at a random moment.
For N+1 (11 total instances), capacity drops below the needed N=10 only if 2 or more instances are down simultaneously:
P(down≥2∣n=11,q)=1−(011)(1−q)11−(111)q(1−q)10 =1−0.994991−0.004998=0.0000114(0.00114%)For N+2 (12 total instances), capacity drops below 10 only if 3 or more are down simultaneously:
P(down≥3∣n=12,q)=1−k=0∑2(k12)qk(1−q)12−k =1−0.994537−0.005450−0.0000137=0.0000000209(0.0000021%)Both numbers are tiny snapshot probabilities; the more useful reading is as the expected fraction of the year the system spends in a shortfall state, converted into expected annual downtime minutes by taking that same fraction-of-time-in-shortfall and multiplying it by the number of minutes in a year, 525,600 (365 days x 24 hours x 60 minutes), the standard way a fraction-of-time becomes an annual downtime figure:
N+1: 0.0000114×525,600≈6.0 minutes/year N+2: 0.0000000209×525,600≈0.011 minutes/year(≈0.66 seconds/year)So under this repair-window-conditioned model, N+1 carries about 6 minutes/year of expected capacity-shortfall exposure, and N+2 cuts that to about 0.01 minutes/year, roughly a 548x reduction, not because any instance's individual failure rate changed, but because a shortfall now requires a second failure to land inside the narrow repair window of the first, and adding a spare pushes that bar from "2 simultaneous" to "3 simultaneous," a much rarer event once q is small. Whether that ~6-minute-a-year difference is worth one extra instance's cost is exactly the trade-off to walk through out loud: for a service where even a few minutes of capacity shortfall risks an SLA breach, cutting expected exposure by roughly two and a half orders of magnitude for one extra instance is usually cheap insurance; for an internal batch service, shortfall risk this small to begin with is very likely not worth the extra spend.
Common concrete instances of the same reasoning: UPS/power-supply sizing (N+1 power modules in a rack survive one PSU failure; N+2 covers one failed unit plus one more failing during the swap), and network device sizing (N+1 top-of-rack switches vs N+2 when switch firmware upgrades take units offline for extended maintenance windows, effectively acting like a "planned failure" that N+1 alone can't absorb if an unplanned one happens at the same time).
Trade-offs & pitfalls
- N+2 isn't "more reliable" in a vacuum, it's specifically insurance against overlapping failures; if your MTTR is minutes and failures are rare and independent, N+1 is usually sufficient and N+2 is paying for a scenario that almost never occurs.
- Fault-domain correlation matters more than the raw redundancy count: N+1 spread across a single rack doesn't protect against a rack-level power failure taking out several "independent" instances at once; redundancy has to be placed in genuinely independent failure domains (different racks, AZs, or power feeds) or the N+1/N+2 math above doesn't hold, because the independence assumption breaks.
- A common wrong turn: treating N+1 as "one extra instance total" when instances are correlated (e.g., all on the same physical host or the same AZ). The formula only protects capacity if the spare's failure mode is independent of the others.
- N+2 costs roughly 20% more standing capacity than N+1's 10% here; that recurring cost has to be justified against the downtime cost it avoids, not assumed.
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.