Failure Modes

Before you can handle failure, you have to name it. A failure mode is a specific way a system stops doing what it is supposed to do. This chapter catalogs the failure modes that front end and back end developers meet most often, and how to reason about each one.

Transient vs. Permanent Failures #

A transient failure is temporary: a request times out, a lock is briefly held, a node is mid-restart. Retrying later may succeed. A permanent failure will not resolve on its own: a malformed request, a deleted record, an expired credential. Retrying only wastes resources.

The single most important classification you make at a failure site is: can retrying help? Get it wrong and you either give up too early or hammer a dead dependency.

async function fetchWithRetry(url: string, attempts = 3): Promise<Response> {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url);
    if (res.ok) return res;
    // 4xx is permanent — do not retry. 5xx / network is transient.
    if (res.status >= 400 && res.status < 500) {
      throw new Error(`Permanent failure: ${res.status}`);
    }
    await new Promise((r) => setTimeout(r, 2 ** i * 100));
  }
  throw new Error("Exhausted retries");
}

Partial Failure #

Distributed systems rarely fail all at once. In a partial failure, some components succeed while others fail. A page loads but the recommendations widget errors; three of five writes commit; a batch job processes 900 of 1000 rows.

Partial failure is dangerous precisely because the system looks alive. Design for it explicitly:

Cascading Failure #

A cascading failure starts small and amplifies. One slow database makes requests pile up, which exhausts the connection pool, which makes healthy services time out waiting, which spreads the outage outward.

The classic defenses:

Data Corruption and Inconsistency #

Not every failure is loud. Silent failures corrupt data or leave it inconsistent without throwing anything. A truncated write, a race condition, a currency stored as a float — these produce wrong answers, not error pages.

Guard against them with validation at boundaries, database constraints, transactions, and checksums. Prefer a system that crashes loudly over one that quietly returns wrong results.

Cardinality of Oops: Why You Should Count Your Failure Modes #

Failure is not one state. It multiplies.

A request can fail because the network is offline, the server is slow, the token expired, the response shape changed, the write partially committed, the retry duplicated work, or the user navigated away halfway through. Each dependency, branch, timeout, retry, cache, queue, and fallback adds another way the system can stop matching your intent.

Counting failure modes is useful because it makes that multiplication visible. You do not need a perfect formal proof. Start with a plain list:

The point is not to make the list frighteningly large. The point is to notice when a design has more distinct failure cases than the code, tests, alerts, or user interface acknowledge. If a checkout flow has twelve meaningful failure modes and one generic "Something went wrong" branch, the system is not simpler. It is merely hiding complexity.

Counting is only step one. The real payoff comes from deciding which failures are harmless delays, which need user action, and which can split reality into "paid here, unpaid there." That classification is the work of Whose Fault Is It Anyway?.

A Shared Vocabulary #

Front end and back end developers handle failure best when they share language. When both sides agree on what "transient," "partial," and "cascading" mean — and on which status codes and error shapes represent them — the whole system becomes easier to reason about and recover.

Failure Modes on the Front End #

Front end code lives in a hostile environment: flaky networks, slow devices, and users who close tabs mid-flow. Common modes:

Design principle: every remote call has at least three visible states — loading, success, and error — and the error state must offer a way forward.

Failure Modes on the Back End #

Back end services fail in ways the front end never sees directly:

The back end owns the contract it presents to clients. When it fails, it should fail with clear, actionable errors and correct status codes — not leak stack traces or hang.

The goal is not to prevent all failure. It is to make failure visible, bounded, and recoverable.


Related chapters: Whose Fault Is It Anyway? · Error Handling Without Fear · Keep Informed and Carry On