InfoWok

Failure Modes and Resilience Patterns: Timeouts, Retries, Breakers and Bulkheads (2026)

Most outages start small: one dependency gets slow and everything that calls it slowly fills with waiting work. This lesson explains how failures spread, and the five tools that stop them: timeouts, careful retries, circuit breakers, bulkheads and load shedding, with the arithmetic and a toy test.

SK
Sukhveer Kaur
Published October 24, 2026
11 min read
Open in ChatGPT Open in Claude
Failure modes and resilience patterns: a slow service behind a timeout, a circuit breaker and a bulkhead, beside the line fail fast, fail smallSoftware Architecture
FAILURE MODES AND RESILIENCE
On this page +

At 2 a.m. nothing is down. A single recommendations service has become slow: it answers, but in eight seconds instead of eighty milliseconds. Twenty minutes later the checkout page is failing, the login service is timing out, and the dashboard says every service is up.

Nothing crashed. The services that call the slow one are holding a worker for every waiting request, and workers are finite. This lesson is about how that spreads, and the five tools that stop it. One idea runs through all of them: fail fast, fail small. The follow-up lesson, failure modes: follow-ups, covers the interview half: “what happens when X is down?”, health checks, hedged requests, error budgets and chaos testing.

Intermediate30 min
Before you start
Remember from the message delivery lesson

Why is retrying a message safe only if the work is idempotent? Because a retry is a second delivery. Retries between services have the same property, plus a new problem: they add load exactly when the system can least afford it.

Key takeaways
  • Slow is often worse than down: callers wait, hold workers, and the failure spreads upward.
  • Put a timeout on every outbound call, and pass the remaining time down the chain.
  • Retries are load. Retry at one layer, back off with jitter, stop after a few tries, and cap the total with a budget.
  • A circuit breaker rejects calls to a failing dependency so it can recover. A bulkhead gives each dependency its own pool so one cannot take everything.
  • Shed excess load early and degrade on purpose, so the work you accept is finished.
After this lesson you can

Explain how one slow dependency takes down a system, choose timeouts, retries, breakers, bulkheads and load shedding for a given call path, and say what each one costs.

How One Slow Service Takes Down Everything#

A request to service A calls B, and B calls C. Each service has a limited number of workers: threads, connections, or in-flight slots. When C slows down, B’s workers wait on C. When all of B’s workers are waiting, B stops answering, so A’s workers wait on B. A and B are healthy code. They have simply run out of workers.

Microsoft’s Circuit Breaker guidance describes exactly this: blocked requests hold critical resources such as memory, threads and database connections, and exhausting them can fail unrelated parts of the system. Google’s SRE book defines a cascading failure as one that grows over time through positive feedback. Overloaded servers miss deadlines, the work they did is wasted, and clients retry, which adds more load.

Predict first

A dependency that is down returns an error in a few milliseconds. One that is slow returns nothing for eight seconds. Which one does more damage to its callers?

The slow one. A dead dependency makes callers fail quickly and free their workers. A slow one holds the workers. That is why the first tool is not about errors at all.

Timeouts and Deadlines#

Amazon’s Builders’ Library states the rule plainly: set a timeout on any remote call, covering both the connection and the request. Microsoft’s retry guidance adds the order: set timeouts on every outbound call before you add retry logic, because a retry strategy is only as good as the timeout on each attempt.

The hard part is the number:

  • Too high: workers are still held while waiting.
  • Too low: healthy calls fail and get retried, which adds load.

Amazon’s method is to pick an acceptable rate of false timeouts, an assumed 0.1% in the article’s example, and read the matching latency percentile of the downstream service, p99.9 in that example. It does not work for calls over the internet, where network latency has to be added, or when p99.9 is close to the median, where some padding helps.

Do not guess the number

A fixed default such as “30 seconds” is a guess. Start from the dependency’s measured latency.

Deadlines travel down the chain#

A timeout covers one call. A deadline is the time the original caller will wait in total, and each hop should know how much is left. Google’s SRE book says servers should propagate deadlines instead of inventing new ones, and check the time left before each stage of work. Amazon does the same, passing the remaining time at each hop so the last service knows how long its answer is useful.

The reason is wasted work. A client gives up after an assumed 10 seconds, the server reaches its request at second 11 and does the work anyway, and nobody reads the answer. The client has already retried, so under overload the waste feeds the loop.

Retries: Selfish by Design#

Retries are selfish

Retries fix brief faults, such as a dropped connection or a server restarting. Amazon’s engineers call retries “selfish” (a client that retries spends more of the server’s time to improve its own chance of success). That is fine when failures are rare. It is harmful when the server is already overloaded.

Retries multiply across layers#

Amazon’s example makes the danger concrete:

  • A five-deep stack of services, ending in a database.
  • Three tries at each layer, and each layer retries on its own.
  • The database sees 3 × 3 × 3 × 3 × 3 = 243 times the original load.

Microsoft’s guidance gives a smaller case with the same shape: two layers with three retries each make nine attempts on the target.

A toy test shows the effect at a smaller scale. Assume 1,000 requests a second, a server that can serve 1,200 a second, and a five-second period when it fails everything.

Client policyPeak attempts reaching the server
No retries1,000 a second
3 immediate retries4,000 a second
3 retries with a 10% retry budget≈ 1,180 a second
3 immediate retries behind a circuit breaker1,000 a second

The toy server only drops excess work. It does not slow down or collapse, so it understates the damage of a real overload. It is enough to show where the extra load comes from, and what removes it.

Backoff, jitter and a budget#

Four rules keep retries from becoming the outage.

  1. Wait between tries, and wait longer each time. Exponential backoff multiplies the delay after each failure, up to a cap. gRPC’s connection backoff, which governs how a client reconnects to a server, uses a 1 second start, a 1.6 multiplier, a 120 second cap and 20% jitter.
  2. Add jitter, a random amount of delay. Without it, clients that failed together retry together. Amazon explains that backing off alone often does not help under overload because of correlation: calls that failed at the same moment come back at the same moment.
  3. Stop after a few tries. Microsoft’s Retry Storm antipattern is the endless while(true) retry: requests are only valid for a limited time, and retrying forever stops the service recovering.
  4. Keep a retry budget. Both Microsoft and Google suggest a limit across all requests in a process, with 60 retries a minute as the example. When the budget is spent, fail instead of retrying. Amazon limits retries locally with a token bucket, and says the AWS SDK has done so since 2016.

A second toy test shows what jitter does. Assume 1,000 clients all fail at the same instant and retry three times, with a 1 second base that doubles each time. Without jitter, the busiest 100 ms slot gets ≈ 1,000 retries, all of them. With full jitter, it gets ≈ 154, and the retries spread across ≈ 66 slots instead of 3.

What to retry, and where#

  • Only retry what is safe. Amazon’s view is that operations with side effects are not safe to retry unless they are idempotent. Some APIs accept an idempotency token for exactly this reason. The message delivery lesson shows how a server dedupes repeats.
  • Only retry what can succeed. A 400 Bad Request will not improve on a second try. Retry timeouts, connection failures and 503 responses, and honour a Retry-After header when the server sends one.
  • Retry at one layer. Amazon’s advice is to retry at a single point in the stack for most operations, and to switch off built-in retries in the layers below.

Circuit Breakers#

A circuit breaker wraps a call and counts failures. Microsoft’s description has three states:

  • Closed: calls pass through, and failures are counted over a time window.
  • Open: after too many failures the breaker rejects calls at once, without calling the dependency.
  • Half-open: after a wait, a few trial calls go through. If they succeed the breaker closes, and if one fails it opens again.
Alert on every trip

Fowler’s write-up adds that you should alert when it trips, because an open breaker is information. The half-open state matters: Microsoft notes it stops a recovering service being flooded the moment it comes back.

Breakers are not free. Amazon’s engineers point out that circuit breakers introduce modal behaviour into a system that can be difficult to test, and can add significant time to recovery. They prefer limiting retries locally with a token bucket. Microsoft lists more things to decide:

  • Scope. One breaker for a dependency with several independent parts, each on its own server, can hide a healthy part behind a failing one.
  • Fallback. The caller needs something to do when the breaker is open: a cached answer, a default, or a clear error.
  • Not for everything. A breaker adds overhead around local in-memory resources, and message-driven systems often rely on retry queues and dead-letter queues instead.

Bulkheads#

A ship’s hull is divided into watertight sections so one breach does not sink it. The Bulkhead pattern does the same for software: give each dependency its own pool of workers or connections, so a failure in one fills only its own pool.

Microsoft’s examples include a connection pool per downstream service, a service instance per client, and separate queues per workload. The costs are the usual ones for isolation: unused capacity in one pool cannot help another, and every partition needs sizing and monitoring. Microsoft also notes that AI and inference workloads often need strict bulkheads, because model deployments have quotas and concurrency limits.

Two halves of one problem

The bulkhead limits how much damage a slow dependency can do while you wait. The breaker stops the waiting.

Sizing a pool: Little’s law#

How big should the recommendations pool be? Use Little’s law. All numbers here are assumptions. Assume the page calls recommendations 100 times a second and a healthy call takes 80 ms.

text
in flight = rate x time
          = 100/s x 0.08 s = 8
pool of 20 = 2.5x headroom
sick: 100/s x 8 s = 800 wanted
cap at 20, rest fail fast

In a shared pool of 20, the sick dependency would want 800 workers and take all 20 from payments.

In its own pool it takes 20 of its own. Those 20 workers finish only 2.5 calls a second at 8 s each, so about 97 of every 100 calls get an immediate rejection or a fallback, and payments keeps its workers.

The price of isolation

In normal times 12 of the 20 workers sit spare and cannot help anyone else.

Load Shedding and Graceful Degradation#

The tools so far protect a caller from a dependency. Load shedding protects a server from its callers. Amazon’s article on overload separates throughput, the requests sent, from goodput, the requests handled correctly and quickly enough to be useful. When a server is past capacity, latency crosses the client’s timeout, goodput falls, and clients retry.

Shedding fixes this by rejecting the excess early. The requests the server accepts are answered quickly, so only the surplus traffic is lost. Practical forms:

  • Limit work in flight. Google’s SRE book suggests returning 503 when more than a set number of requests are in flight.
  • Keep queues short. The same book suggests queue lengths of ≈ 50% or less of the thread pool for steady traffic, so a saturated server rejects early. Amazon bounds how long a request may wait in a queue and drops stale ones.
  • Drop doomed work. If the deadline has already passed, skip the request.
  • Degrade on purpose. Return a cheaper answer, for example search over a cached subset, instead of failing. Google warns that a degraded mode you never exercise is likely to be broken, so run it regularly.

When Not to Use Each Tool#

Every tool has a cost, so name when you would skip it. These are judgement calls, backed where noted by the sources.

ToolSkip or soften it when
RetryThe call is not idempotent, the error is a 400, or the server is already overloaded
Circuit breakerThe resource is local memory, or a queue with a dead-letter queue already absorbs failures (Azure)
BulkheadSpare capacity is scarce and the dependencies rarely fail
Load sheddingTraffic is bursty but short, and a bounded queue would absorb it
FallbackThe wrong answer is worse than none, such as a stale balance on a payment

The last row is the one people forget. A cached recommendation list is a fine fallback. A cached account balance that lets someone overspend is not.

Putting It Together: One Protected Call#

For one outbound call, the layers sit in a sensible order.

Check the remaining deadline first, then take a slot in the dependency’s pool. If the breaker is open, skip the call. Otherwise make one attempt with a timeout. If it fails and the budget has tokens, retry after a jittered delay. When nothing works, return the fallback. Every failure feeds the breaker and spends budget.

Where AI Systems Hit This#

Model APIs are dependencies too

Calls to a model API are slow and rate limited, so every pattern here applies. Honour Retry-After, cap retries, and give model calls their own bulkhead. An agent that times out and calls a tool again is the double-delivery problem from the message delivery lesson: use a stable ID so the tool ignores the repeat.

Try It: Count the Retries#

Exercise · What does the database see?8 min

A mobile app calls an API gateway, which calls an order service, which calls an inventory service, which calls a database. Each of the four callers (app, gateway, order, inventory) makes up to 3 attempts. The inventory service normally sends the database 200 calls a second, and then the database starts failing every query. Decide:

  1. How many attempts can the database see at worst for each original request?
  2. What is the offered load in that case?
  3. Name two changes that bring it down.
Hint

Multiply the attempts at each layer. Then think about which layer should retry, and what limits the total.

One answer
text
4 hops x 3 attempts each
= 3 x 3 x 3 x 3 = 81 attempts
200 calls/s x 81 = 16,200/s
Fix: retry at one layer only
Cap total with a retry budget
Breaker stops calls entirely

Retries multiply across layers, so the load is the product, not the sum. The simplest fix is to keep retries at one layer, such as the gateway, and switch them off below. A retry budget, for example 60 a minute per process, then caps the total even when many requests fail. A circuit breaker on the inventory-to-database call would stop the calls once the database is clearly down.

In an interview

When asked “what happens if this dependency gets slow?”, do not stop at “add a retry”. Say: “Every call has a timeout and a deadline. Each dependency gets its own pool. A breaker rejects calls once failures pass a threshold. Retries happen at one layer, with backoff, jitter and a budget, only for idempotent calls. The server sheds excess load, and users get a fallback.”

The cost: more configuration, and failure behaviour that must be tested.

Check Yourself#

Check yourself5 questions · answer, then open
1.Why can a slow dependency do more damage than a dead one?

A dead one fails quickly, so callers release their workers. A slow one makes callers wait while holding workers, connections and memory, until the callers themselves cannot serve anyone.

2.Five layers each retry three times. What load does the bottom layer see, and what is the fix?

3 to the power of 5, which is 243 times the original load. Retry at a single layer, add backoff with jitter, and cap the total with a retry budget.

3.What does jitter add that backoff alone does not?

It spreads retries in time. Without it, clients that failed together back off by the same amount and return together, recreating the spike.

4.What is the difference between a circuit breaker and a bulkhead?

A breaker stops calls to a dependency that is failing. A bulkhead limits how many workers or connections any one dependency can use. One removes the waiting, and the other limits the damage while waiting.

5.What is goodput, and how does load shedding protect it?

Goodput is the requests that are handled correctly and fast enough to be useful. Shedding rejects excess early, so accepted requests are answered before client timeouts instead of everything becoming slow and useless.

Learning tip

Draw one call path from memory with six boxes: deadline, bulkhead, breaker, timeout, budgeted retry, fallback. Then break a dependency on paper and trace what each box does. Redo it tomorrow with a different dependency.

In One Picture#

text
Slow   holds workers, spreads up
Time   timeout every call,
  pass the deadline down
Retry  one layer, backoff +
  jitter, few tries, budget
Break  stop calling a sick
  dependency, probe to recover
Wall   a pool per dependency
Shed   reject excess early,
  degrade on purpose

Fail fast, fail small: limit the waiting, limit the retrying, and limit what any one failure can reach.

Where to go from here

Next in the course: observability for system design interviews, which is how you notice any of this is happening.

Frequently asked questions

What is a cascading failure? +
A failure that grows over time through positive feedback. Google's SRE book describes how overload makes a service miss deadlines and crash, which pushes its load onto the remaining servers, and clients retrying makes it worse. One slow dependency can leave every service above it with all its workers waiting.
How do you choose a timeout? +
Amazon's guidance is to pick an acceptable rate of false timeouts, such as 0.1%, and set the timeout to the matching latency percentile of the downstream service, p99.9 in that example. Too high wastes resources while waiting. Too low causes extra retries and can turn a small slowdown into an outage.
Why do retries make outages worse? +
Retries add load to a system that is already struggling. Amazon's example is five layers of services where each layer tries three times: the database sees 3 x 3 x 3 x 3 x 3 = 243 times the original load. Retry at one layer only, use exponential backoff with jitter, and cap the total with a retry budget.
What is the difference between a circuit breaker and a bulkhead? +
A circuit breaker stops calling a dependency that is failing, so callers fail fast and the dependency can recover. A bulkhead limits how many workers or connections any one dependency can use, so a slow dependency cannot take every worker. They are used together.
What is load shedding? +
When a server approaches overload it rejects excess requests early, so the ones it accepts are answered quickly. Amazon calls the useful part of the traffic goodput. Without shedding, latency passes the client timeout, work is wasted, and clients retry, which adds still more load.

References

  1. Azure Architecture Center — Circuit Breaker pattern
  2. Azure Architecture Center — Bulkhead pattern
  3. Azure Architecture Center — Transient fault handling and retry guidance
  4. Azure Architecture Center — Retry Storm antipattern
  5. Amazon Builders' Library — Timeouts, retries, and backoff with jitter
  6. Amazon Builders' Library — Using load shedding to avoid overload
  7. Google SRE Book — Addressing Cascading Failures
  8. Martin Fowler — Circuit Breaker
  9. gRPC — Connection Backoff Protocol
Written by
Sukhveer Kaur
Sukhveer KaurSoftware Developer & AI Engineer

Sukhveer is a software developer specialising in AI systems and backend engineering. She has hands-on experience designing agentic AI applications, working with large language model pipelines, autonomous agent frameworks, and cloud-native services in Java and Python. At InfoWok, she bridges the gap between cutting-edge AI research and practical implementation — helping developers understand and apply emerging technologies through clear, experience-backed writing.

New AI engineering guides, the day they ship

Real Python, production depth. No digest spam.

Comments