Resilience & Scalability
Fault-tolerance and scale patterns an analyst specifies in NFRs — load balancing, circuit breaker, retry with backoff, bulkhead, graceful degradation, rate limiting, caching, and zero-downtime migration.
14 questions
JuniorTheoryVery commonWhat is fault tolerance, and why does a distributed system need dedicated resilience patterns?
What is fault tolerance, and why does a distributed system need dedicated resilience patterns?
Fault tolerance is keeping a system serving correct results while parts of it fail. Resilience patterns — timeouts, retries, circuit breakers, bulkheads, fallbacks — contain a single failure so it degrades one feature instead of cascading and taking the whole distributed system down.
Common mistakes
- ✗Equating fault tolerance with buying bigger hardware instead of containing failures
- ✗Assuming a distributed system is resilient by default without explicit patterns
- ✗Thinking resilience prevents failures rather than limiting their blast radius
Follow-up questions
- →Which resilience pattern would you reach for first, and why?
- →How does a timeout differ from a retry in what it protects?
MiddleTheoryVery commonWhat is load balancing, which algorithms exist (round-robin, least-connections, hash), and what is a sticky session?
What is load balancing, which algorithms exist (round-robin, least-connections, hash), and what is a sticky session?
A load balancer spreads requests across identical instances so none is overloaded and dead ones are skipped. Round-robin rotates evenly, least-connections picks the least-busy instance, and hash routes by a client key. A sticky session pins a client to one instance — good for in-memory state but bad for balance and failover.
Common mistakes
- ✗Confusing round-robin (rotation) with least-connections (load-aware) routing
- ✗Thinking a sticky session improves balance rather than harming it
- ✗Believing a balancer replicates each request to every instance
Follow-up questions
- →Why does a sticky session complicate scaling out and failover?
- →When would hash-based routing be preferable to round-robin?
MiddleTheoryVery commonWhat is the difference between scaling a service out and scaling it up, and what makes a service horizontally scalable?
What is the difference between scaling a service out and scaling it up, and what makes a service horizontally scalable?
Scaling up (vertical) means a bigger machine for one instance — simple but capped, and a single point of failure. Scaling out (horizontal) means more instances behind a load balancer, so capacity grows almost linearly and survives an instance loss. A service scales out only if it is stateless — session and data in a shared store — so any instance serves any request.
Common mistakes
- ✗Swapping the definitions of scaling up and scaling out
- ✗Calling a service that keeps session in instance memory horizontally scalable
- ✗Ignoring the shared-store requirement that makes instances interchangeable
Follow-up questions
- →Why is a stateful service hard to scale out even behind a balancer?
- →When does scaling up remain the pragmatic first choice?
JuniorTheoryCommonWhere is caching implemented — client, CDN, gateway, or server — and what does each layer cache?
Where is caching implemented — client, CDN, gateway, or server — and what does each layer cache?
Caching stacks across layers. The client caches assets per Cache-Control; a CDN caches content at edge nodes near users; the API gateway caches common responses to shield the backend; the server caches query results in a store like Redis. Layers nearer the user cut latency; nearer the data cut recomputation.
Common mistakes
- ✗Thinking caching happens at only one place instead of stacked layers
- ✗Letting a CDN cache per-user private responses at a shared edge
- ✗Ignoring
Cache-Controlas the contract that governs each layer
Follow-up questions
- →Which layer would you cache a per-user dashboard at, and why?
- →How does
Cache-Controltell each layer what it may store?
MiddleDesignCommonBlack Friday triples the expected load on an online store. Checkout must stay up even if some features suffer. Which levers do you pull, and in which order, to protect the checkout path? Cover pre-scaling capacity, shedding non-essential load (recommendations, analytics, search suggestions), protecting shared dependencies (payment, inventory) with timeouts and circuit breakers, and what you deliberately turn off first so the money path keeps working while lower-value features degrade.
Black Friday triples the expected load on an online store. Checkout must stay up even if some features suffer. Which levers do you pull, and in which order, to protect the checkout path? Cover pre-scaling capacity, shedding non-essential load (recommendations, analytics, search suggestions), protecting shared dependencies (payment, inventory) with timeouts and circuit breakers, and what you deliberately turn off first so the money path keeps working while lower-value features degrade.
First, pre-scale ahead of the event — add instances and read replicas while load is still low. Second, protect the money path: wrap payment and inventory calls in timeouts and circuit breakers so a slow dependency fails fast instead of stalling checkout. Third, shed non-essential load by value — turn off recommendations, suggestions, and heavy analytics first. Spend scarce capacity on the checkout path.
Common mistakes
- ✗Scaling reactively during the spike instead of pre-scaling before it
- ✗Shedding the checkout path instead of the optional features first
- ✗Omitting timeouts on payment and inventory so a slow call stalls checkout
Follow-up questions
- →How do you decide the order in which features are shed?
- →Why pre-scale rather than rely on autoscaling during the spike?
MiddleTheoryCommonWhat is a circuit breaker, what are its three states, and what does it protect against?
What is a circuit breaker, what are its three states, and what does it protect against?
A circuit breaker wraps a dependency's calls and stops them once it looks unhealthy. Closed counts failures; Open fails calls fast past a failure-rate threshold; Half-open lets a few trial calls through after a cooldown, closing on success or re-opening on failure. It saves resources on a dead service and stops cascading overload.
Common mistakes
- ✗Naming states by retry speed or cache status instead of Closed/Open/Half-open
- ✗Thinking a breaker retries harder rather than failing fast when Open
- ✗Confusing a circuit breaker with a load balancer over replicas
Follow-up questions
- →How does Half-open avoid slamming a service that just recovered?
- →How does a circuit breaker complement a retry with backoff?
MiddleDesignCommonAn e-commerce product page shows personalized recommendations from a separate recommendation service. That service is optional to the sale but is sometimes slow or down. Design graceful degradation for this dependency: state what the page must still do when recommendations fail, describe the fallback response you would return, and explain how the fallback is triggered (timeout, circuit breaker) so a slow recommendation service never blocks the product page from rendering or the customer from buying.
An e-commerce product page shows personalized recommendations from a separate recommendation service. That service is optional to the sale but is sometimes slow or down. Design graceful degradation for this dependency: state what the page must still do when recommendations fail, describe the fallback response you would return, and explain how the fallback is triggered (timeout, circuit breaker) so a slow recommendation service never blocks the product page from rendering or the customer from buying.
The core content — price, description, add-to-cart — must render independently of recommendations. Wrap the recommendation call in a short timeout and a circuit breaker; when it trips, return a fallback instead of an error: a cached list, best-sellers, or a hidden block. Checkout stays fully functional while one optional widget degrades — silently to the buyer, never a failed page.
Common mistakes
- ✗Letting an optional dependency block the core page or checkout
- ✗Returning an error page instead of a silent fallback for a widget
- ✗Retrying a slow dependency inline rather than timing it out fast
Follow-up questions
- →How do you keep a cached fallback list from going badly stale?
- →Why show best-sellers rather than an empty region on failure?
MiddleDesignCommonA partner integration hammers your public API with 10 000 requests per second, far above their agreed quota, and it is starving other clients. Design the rate-limiting policy: pick where the limit is enforced, which algorithm caps the rate, how you scope the limit to that partner, and specify exactly what the API returns to a caller once they exceed the limit — status code, headers, and body — so legitimate traffic keeps flowing while the abusive partner is throttled.
A partner integration hammers your public API with 10 000 requests per second, far above their agreed quota, and it is starving other clients. Design the rate-limiting policy: pick where the limit is enforced, which algorithm caps the rate, how you scope the limit to that partner, and specify exactly what the API returns to a caller once they exceed the limit — status code, headers, and body — so legitimate traffic keeps flowing while the abusive partner is throttled.
Enforce the limit at the API gateway, before the backend, scoped per API key (the partner), not globally. Use a token-bucket or sliding-window counter to cap requests per second while allowing short bursts. On excess, return HTTP 429 Too Many Requests with a Retry-After header — never a silent drop or 500. Each client has its own bucket, so throttling one partner spares the rest.
Common mistakes
- ✗Applying one global limit instead of a per-partner (per-key) scope
- ✗Returning
200or a silent drop instead of429withRetry-After - ✗Enforcing the limit in the backend after work is already done, not at the edge
Follow-up questions
- →Why does token-bucket allow bursts that a fixed window rejects?
- →How does
Retry-Afterhelp a well-behaved client back off?
MiddleTheoryCommonWhat is retry with exponential backoff and jitter, and when is a retry dangerous?
What is retry with exponential backoff and jitter, and when is a retry dangerous?
On a transient failure the client retries but waits longer each attempt — exponential backoff (1s, 2s, 4s…) — so it does not hammer a struggling service. Jitter randomizes each delay so clients that failed together do not retry in sync. A retry is dangerous when the operation is not idempotent (a retried payment charges twice) or when it amplifies the overload the retries caused.
Common mistakes
- ✗Reversing backoff into ever-shorter delays instead of ever-longer ones
- ✗Omitting jitter and letting synchronized clients cause a thundering herd
- ✗Retrying a non-idempotent operation and duplicating its effect
Follow-up questions
- →How does an idempotency key make a retry safe for a payment?
- →Why can retries without a cap deepen an outage they did not start?
SeniorDesignCommonA payments platform must survive the loss of an entire data centre. As the analyst, you own the non-functional requirements. Define the two recovery targets RTO (recovery time objective) and RPO (recovery point objective), state realistic values for a payments system and what each one costs, and name the resilience patterns and topology you would specify — replication, multi-region deployment, failover, backups — so the business signs off knowing exactly how much downtime and data loss is tolerated.
A payments platform must survive the loss of an entire data centre. As the analyst, you own the non-functional requirements. Define the two recovery targets RTO (recovery time objective) and RPO (recovery point objective), state realistic values for a payments system and what each one costs, and name the resilience patterns and topology you would specify — replication, multi-region deployment, failover, backups — so the business signs off knowing exactly how much downtime and data loss is tolerated.
RTO is how long you may be down after a failure; RPO is how much recent data you may lose. For payments both must be tight — RTO of minutes, RPO near zero — because lost transactions are money and trust. Near-zero RPO needs synchronous replication to a second region; low RTO needs a warm/hot standby with automated failover, not a manual restore from nightly backups.
Common mistakes
- ✗Swapping the definitions of RTO (downtime) and RPO (data loss)
- ✗Accepting nightly backups and manual restore for a near-zero-RPO system
- ✗Staying single-region because one machine is assumed never to fail
Follow-up questions
- →Why does a near-zero RPO force synchronous rather than async replication?
- →How does a hot standby lower RTO compared with restoring from backups?
MiddleTheoryOccasionalWhat is the bulkhead pattern, and what exactly does it isolate?
What is the bulkhead pattern, and what exactly does it isolate?
Named after a ship's watertight compartments, the bulkhead pattern partitions a shared resource so one overloaded consumer cannot drain it for everyone. Each dependency gets its own pool — separate thread and connection pools or queues — with fixed limits, so a hung service saturates only its pool while others keep capacity. It isolates resource contention, not the failure itself.
Common mistakes
- ✗Thinking bulkhead shares one pool rather than partitioning per dependency
- ✗Confusing bulkhead (resource isolation) with hiding the error from users
- ✗Believing it isolates the failure event rather than resource contention
Follow-up questions
- →How does a per-dependency thread pool stop one hang from spreading?
- →How do bulkheads and circuit breakers complement each other?
SeniorDesignOccasionalFor a shopping cart you decide to favour availability over strong consistency (AP over CP): the cart must always accept add and remove actions, even during a partition, and replicas may briefly diverge. Explain concretely what the user could observe as a result of this choice, why that trade-off is acceptable for a cart specifically, and where in the purchase flow you must switch back to strong consistency so the AP cart does not cause real harm.
For a shopping cart you decide to favour availability over strong consistency (AP over CP): the cart must always accept add and remove actions, even during a partition, and replicas may briefly diverge. Explain concretely what the user could observe as a result of this choice, why that trade-off is acceptable for a cart specifically, and where in the purchase flow you must switch back to strong consistency so the AP cart does not cause real harm.
Choosing AP keeps the cart writable during a partition, so replicas can diverge — a removed item might briefly reappear or a count go stale until they reconcile. That is fine: a cart is a private, re-editable draft, and blocking adds loses sales. At checkout you switch to CP — price, stock, and payment must be strongly consistent — so you never buy out of stock or at a stale price.
Common mistakes
- ✗Thinking AP guarantees identical replicas rather than tolerating divergence
- ✗Keeping the relaxed AP model through payment and inventory
- ✗Believing an AP cart makes the cart read-only during a partition
Follow-up questions
- →How would you reconcile two diverged cart replicas after a partition?
- →Why is overselling unacceptable at checkout but a stale count fine in the cart?
SeniorDesignOccasionalA stakeholder asks you to design an order system that is simultaneously consistent, available, and partition-tolerant — all three CAP guarantees at once — across two data centres. During a network partition, write requests still arrive on both sides. Explain where this ideal actually breaks, which single guarantee you must give up while partitioned and why you cannot keep all three, and how you would justify the CP-versus-AP choice for order placement to that stakeholder in business terms.
A stakeholder asks you to design an order system that is simultaneously consistent, available, and partition-tolerant — all three CAP guarantees at once — across two data centres. During a network partition, write requests still arrive on both sides. Explain where this ideal actually breaks, which single guarantee you must give up while partitioned and why you cannot keep all three, and how you would justify the CP-versus-AP choice for order placement to that stakeholder in business terms.
CAP says a partition forces a choice between consistency and availability; with a healthy network you get both, so all three hold only when not partitioned. When both data centres take writes but cannot sync, they either reject writes (CP) or accept divergent ones (AP). For order placement I choose CP — queue or refuse minority-side writes — because a conflicting order costs more than a brief retryable outage.
Common mistakes
- ✗Claiming modern databases deliver all three guarantees simultaneously
- ✗Treating the C-versus-A choice as permanent rather than partition-time only
- ✗Dropping partition tolerance instead of distributing at all
Follow-up questions
- →How would an AP order system reconcile the conflicting writes afterward?
- →Which order operations could safely be AP while placement stays CP?
SeniorDesignOccasionalYou must change the schema of a table that is written to about 1 000 times per second — say, rename a column or split it into two — with zero downtime and no lost writes. A single locking ALTER TABLE would stall every writer. Describe the phased approach you would use: how new and old schema coexist, how the application writes during the transition, how existing rows are backfilled, and how you cut over and clean up so no writer is ever blocked.
You must change the schema of a table that is written to about 1 000 times per second — say, rename a column or split it into two — with zero downtime and no lost writes. A single locking ALTER TABLE would stall every writer. Describe the phased approach you would use: how new and old schema coexist, how the application writes during the transition, how existing rows are backfilled, and how you cut over and clean up so no writer is ever blocked.
Use an expand-migrate-contract rollout, not one blocking ALTER. Expand: add the new column additively so old and new coexist. Migrate: dual-write to both, then backfill existing rows in small batches so no long lock is held. Once new is populated and reads switch to it, contract: stop writing the old column and drop it later. No step takes a global lock, so writers never block.
Common mistakes
- ✗Running a single blocking
ALTERinstead of an additive phased change - ✗Dropping the old column in the same release rather than a later one
- ✗Backfilling the whole table in one statement instead of small batches
Follow-up questions
- →Why must the drop of the old column wait for a later release?
- →How does dual-write keep the two columns consistent during migration?