← Back to all articles
Owen Reed

Designing for Failure

A practical look at timeouts, retries, partial results, and the small details that make systems trustworthy.

reliabilitybackendsystems

Reliable systems are not systems where nothing goes wrong. They are systems where failure has a shape that people and software can understand.

Every dependency needs a deadline

An outbound request without a timeout is an unfinished decision. The caller cannot know whether to wait, retry, or offer a fallback.

const response = await fetch(endpoint, {
  signal: AbortSignal.timeout(3_000),
});

The right timeout depends on the operation, but an explicit timeout is always more useful than an accidental one imposed by a load balancer or browser.

Retries are not free

Retries help with brief network blips, but they can amplify an outage when every caller retries at the same time.

Strategy Good for Main risk
Fixed delay Simple internal jobs Synchronized retry storms
Exponential backoff Transient service failures Slow recovery if uncapped
Jittered backoff Many concurrent clients More complex debugging
No retry Non-idempotent operations More visible transient errors

For a safe retry, ask whether the operation is idempotent and whether the server can recognize a duplicate request.

Partial success is still a state

A dashboard that loaded eight of ten widgets should not pretend it loaded ten. Show the successful data, identify the unavailable pieces, and provide a specific recovery action:

  • Refresh billing data
  • Reconnect to search
  • View cached results from 10:41

This is better than replacing the entire screen with a generic error.

A failure review checklist

  • Is the timeout visible in configuration?
  • Are retries bounded?
  • Do logs distinguish timeout from cancellation?
  • Can the user keep useful partial results?
  • Is there a runbook for the alert?

Failure handling is product design with a stopwatch attached.