Back to blog
Cascading failure spreading across data center server racks
System Design

Cascading Failure: When One Command Takes Down Everything

Aug 11, 2026 7 min read Avinash Tyagi
cascading failure cascading failures cascade failure distributed systems reliability retry storm circuit breaker blast radius AWS S3 outage load shedding system design

On February 28, 2017, an engineer at Amazon Web Services ran a routine playbook command meant to remove a small number of servers from an S3 billing subsystem in US-EAST-1. One parameter was typed larger than intended, and the automation removed far more servers than anyone wanted gone. Within minutes S3 was effectively down, and because a huge fraction of the internet stores data in S3, sites with no connection to the mistyped command started returning errors.

That is a cascading failure: a single small fault that propagates through dependencies until a large part of the system falls over. In complex systems it is one of the clearest dividers between engineers who have felt production pain and those who have only read about it.

What is a cascading failure?

A cascading failure is a fault in one part of a system that triggers failures in dependent parts, spreading until the system degrades or collapses. The initial fault is often trivial, and the propagation does the real damage. Three ingredients almost always show up together when systems fail this way: a shared dependency many components rely on, limited capacity so survivors cannot absorb the extra work, and a feedback loop (usually retries or load redistribution) that turns a small overload into a growing one. Remove any one ingredient and a fault stays contained.

Diagram showing a cascading failure spread across servers behind a load balancer in three stages
A cascading failure in three stages: healthy, two servers fail and load shifts to survivors, then the whole fleet collapses.

Almost every cascade moves through three phases. The trigger is the initial fault, usually survivable on its own. In propagation, work from the failed component shifts to the survivors, clients retry, and the survivors slow down, miss health checks, and drop out, shifting load onto an ever smaller pool. The collapse is the end state, and recovery is slow because everything restarts at once.

The AWS S3 story, step by step

The 2017 outage shows every phase cleanly. The trigger was the mistyped parameter, which removed servers supporting two load-bearing S3 subsystems: the index subsystem, which tracks the location of all objects, and the placement subsystem, which allocates new storage. Once enough capacity was gone, S3 could not serve reads or accept writes.

Propagation was brutal because both subsystems required a full restart, and reloading their vast state took hours. The collapse reached far beyond Amazon, since countless services stored assets in S3. The detail engineers love to repeat is that AWS's own status dashboard depended on S3, so for a while it could not even display that anything was wrong.

The public post-incident writeup drove a key fix: the tooling was changed to refuse to drop any subsystem below its minimum required capacity. The system was taught to say "no" to a command that would push it past a safe threshold.

Why one removed server becomes ten dead ones

Losing capacity does not subtract load, it concentrates it. Picture ten servers at 60 percent utilization behind a load balancer. Two fail, and their traffic spreads across the remaining eight, pushing them near 75 percent as every survivor sees its servers request rate climb. If a third tips over, latency rises, health checks time out, the load balancer removes the "unhealthy" servers, and their traffic shoves onto an even smaller group. Left unchecked, this load redistribution clears the whole fleet.

Retries pour fuel on the fire. During an overload every failed request becomes two or three, so retries increase the load on a struggling service exactly when it needs relief. This is a retry storm.

naive_retry.pypython
# Dangerous during an overload: retries pile on instantly
def fetch(url):
    for attempt in range(5):
        try:
            return http_get(url, timeout=1.0)
        except RequestError:
            continue  # immediate retry, no delay, no backoff
    raise ServiceUnavailable(url)

Multiply that loop across thousands of clients and a service that was merely slow is buried under resource exhaustion.

The patterns that stop the spread

Cascading failures are well understood, and the Google SRE book devotes a chapter to them. The countermeasures map onto a handful of patterns.

Limit the blast radius

Partition the system into cells or shards so a failure is contained to one slice of traffic. Shuffle sharding, separate thread pools, and the bulkhead pattern all isolate resources so one overloaded pool cannot starve the others. This is the same principle behind a single failure domain in an API gateway, applied more broadly.

Circuit breakers

When error rates cross a threshold, the breaker opens and calls fail fast locally instead of piling onto the sick service. After a cooldown it lets a trickle through to test recovery. This one pattern breaks the feedback loop that drives most cascades.

Load shedding, rate limiting, and graceful degradation

Near overload, deliberately drop the lowest-value requests and cap how much any client can send, so the server stays alive. Graceful degradation serves cached or partial results instead of nothing. A system that degrades on purpose beats one that collapses by accident.

Exponential backoff with jitter

Backoff waits longer after each failure, and jitter adds randomness so clients do not all retry at the same instant and form a thundering herd.

backoff.pypython
import random, time

def fetch_with_backoff(url, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            return http_get(url, timeout=1.0)
        except RequestError:
            if attempt == max_attempts - 1:
                raise
            delay = 0.1 * (2 ** attempt)          # exponential growth
            time.sleep(delay + random.uniform(0, delay))  # plus jitter
    raise ServiceUnavailable(url)

Timeouts and bounded queues

Every remote call needs a timeout, and every queue needs a bound. An unbounded queue absorbs an overload silently until it exhausts memory. A short, bounded queue that rejects work early gives you backpressure before things break.

How to talk about cascading failures in an interview

This shows up constantly in senior and staff interviews as a follow-up: "what happens when this dependency gets slow?" Strong answers name the mechanism, not just the symptom. Say "the slow dependency causes requests to queue, callers time out and retry, the retries amplify load, and healthy replicas get pulled in through the load balancer," then reach for the countermeasures. Naming the cascading failure risks in your design and citing the AWS S3 incident signals that you have studied real postmortems.

For structured practice on these follow-ups, that is what we built Levelop for, and the Levelop blog covers related topics like zero-downtime database migrations and caching strategies and their failure modes.

Frequently asked questions

What is a cascading failure in distributed systems?

A fault in one component that triggers failures in the components depending on it, until a large portion of the system is degraded or down. It needs a shared dependency, limited spare capacity in the survivors, and a feedback loop such as retries or load redistribution that turns a small overload into a growing one.

What caused the 2017 AWS S3 outage?

An engineer ran a playbook to remove a few servers from an S3 billing subsystem in US-EAST-1, but a typo made the value far larger. It removed servers running the S3 index and placement subsystems, both of which needed a full restart, so S3 was down for hours and the failure cascaded across much of the internet.

How do circuit breakers prevent cascading failures?

When failures to a dependency cross a threshold, the breaker opens and makes calls fail fast locally instead of hitting the struggling service. That breaks the feedback loop and gives the service room to recover. After a cooldown, it lets a few requests through to check if it is healthy again.

What is the difference between a cascading failure and a single point of failure?

A single point of failure is a component whose failure alone takes down the system, a static weakness you can spot on a diagram. A cascading failure is a dynamic process where a survivable fault spreads through dependencies as failures occur. A single point of failure can trigger a cascade, but cascades also happen without one, purely through load redistribution and retries.

How do you prevent retry storms?

Use exponential backoff with jitter, cap the total retries, and shed traffic upstream once load exceeds capacity. Circuit breakers and rate limiting help too, since they stop retries from reaching a service that is already overwhelmed.

Keep reading

System Design

Zero Downtime Database Migration: The Cassandra Story

A zero downtime database migration lets you move off Cassandra while users keep reading and writing. Here are the five phases, using Discord's move to ScyllaDB as the example.

Read article
System Design

Synchronous vs Asynchronous Communication Explained

The choice between synchronous and asynchronous communication shapes latency, failure modes, and coupling. Here is how request-response, fire-and-forget, and event-driven differ, and when to use each.

Read article
System Design

4 Caching Strategies for System Design

Master cache-aside, read-through, write-through, and write-behind patterns. Learn when each strategy wins, what breaks, and how to explain the tradeoffs in system design interviews.

Read article