The standard argument goes like this: the API protects itself with 429s and 503s, clients handle those responses, the system degrades gracefully. The API sheds load; clients manage their own retry logic. Everyone goes home.

That argument is mostly wrong. Not because the API shouldn’t shed load — it should — but because it places failure management at exactly the wrong layer.


The Call Is the Cost

A 429 is not free. It costs a TCP connection, a TLS handshake, and however many microseconds of application logic the ingress controller spent deciding to say no. Under normal traffic, that’s fine. Absorbing 100k requests per second from a misbehaving caller, the overhead of returning polite errors can saturate the ingress before the application knows there’s a problem.

The correct unit of analysis is not “are we returning the right status code?” It’s “did we burn a resource to do it?”

A client-side breaker opens locally. The request never leaves the caller’s memory. No round trip. No blocked thread. No graceful-degradation theater.

The Thread Drain

The second failure mode is worse, and it’s the one that surprises people.

A 429 or 503 still takes time — not much, but some. Client services have bounded thread pools. Here is what happens to a healthy client when a downstream dependency starts returning fast 400s: each worker thread blocks briefly, waits for the response, exits. If the dependency is erratic, threads block longer. With 100 worker threads and a dependency returning 400s in 300ms, you can sustain roughly 333 requests per second before the thread pool saturates. At 334, the client stops accepting new work.

The dependency didn’t take the service down. The act of checking on the dependency did.

We call this the Thread Drain. The client service crashes not because it’s failing, but because it spent all its capacity waiting for something else to tell it how bad things were. A client-side breaker interrupts the Thread Drain by failing immediately and locally — before the round trip, before the block, before the saturation.


What the Mesh Can’t See

Service meshes run the same argument from a different angle: let the proxy handle it, transparently, without touching application code. The argument fails for the same structural reason.

A sidecar proxy sees HTTP status codes and connection timeouts. It cannot see what your application means by “healthy.” If a payment processor is returning 200s with empty data arrays, Envoy doesn’t know those responses are wrong. If an LLM integration is taking 4.5 seconds per token, the mesh has nothing to report.

Most production reliability incidents aren’t simple 500s. They’re what we call Zombie States — the network is functioning, status codes look fine, and something is deeply wrong. The mesh is silent during Zombie States. A breaker configured with knowledge of your business invariants isn’t.

There’s a second structural problem with per-sidecar mesh-based circuit breaking. If a downstream dependency is failing 5% of traffic and you’re running 100 pods, no individual sidecar may accumulate enough failures to trip. Globally, your system is 5% degraded. Every pod thinks it’s fine.

Tripswitch aggregates from all instances. The decision to open a breaker is made on global signal, not pod-local noise. The fleet hears the same call at the same time.


The Moment It Matters

An SRE is on call. The dashboard is green. Two support tickets land about checkout failing. They pull up the payment gateway — 200s across the board, P50 looks clean. P99 is at 4.2 seconds. Checkout service thread pool utilization is at 94%.

No breaker fired. No mesh rule tripped. The postmortem will say “intermittent slowness in third-party payment provider.” What it won’t say: the checkout service was 6% from complete saturation, and the only available signal was a P99 number nobody was watching in a dashboard they didn’t know to check.

A breaker configured to trip on “P99 exceeds 3 seconds” — not “HTTP status is 5xx” — would have opened before that saturation point. The SRE would have gotten paged on a circuit breaker event, not two support tickets and a mystery.


The Right Split

The mesh handles network connectivity. It cannot handle application resilience, because resilience is defined by the caller, not the transport layer.

An API returning 400s tells you it’s struggling. It does not tell you whether your client can afford to wait. The load shedder is not wrong to exist; it is wrong to be the only thing between a dependency failure and a cascade.

The Zombie State is the incident you’ll write a postmortem for next quarter and attribute to a flaky third party. It won’t be a flaky third party. It will be the moment you assumed that “no 5xx” meant “no problem,” and that the API’s self-reported health was the same thing as your system’s actual resilience.

You don’t put the airbag in the wall at the end of the road.