A service call can fail quickly and still create a larger system problem. When every upstream request continues to invoke a downstream dependency that is already failing, each attempt consumes connection capacity, worker time, retry budget, and queue space. The dependency receives traffic it cannot currently serve, while callers spend resources waiting for outcomes that are already strongly correlated with recent failures.

A circuit breaker puts a stateful decision boundary in front of that call. Instead of treating every request as an independent opportunity to try the dependency, it records recent failure state and can reject calls locally for a bounded interval. Recovery is then tested through controlled probes rather than a full return of traffic.

The mechanism does not make a failed dependency healthy. It changes how failure propagates through the caller.

Closed state preserves the ordinary call path

A breaker normally begins in the closed state. Calls pass to the dependency, and their outcomes contribute to whatever failure policy the implementation uses. That policy might count consecutive failures, calculate a failure ratio over a bounded sample, or classify selected outcomes as breaker-relevant.

The classification matters. An HTTP 500, a connection refusal, and a local timeout may indicate dependency failure. An HTTP 400 caused by invalid caller input usually says something different. Treating every non-success result as evidence of dependency health can open the breaker for traffic the dependency handled correctly.

The breaker therefore needs an explicit outcome model. It also needs a sampling model. A threshold of five failures means something different when five calls arrive per hour than when five thousand arrive per second. Ratio-based policies need a minimum sample size or low-volume noise can produce abrupt state changes.

While the breaker remains closed, it adds observation and policy but does not intentionally suppress ordinary calls.

Open state turns remote failure into a local decision

Once the configured trip condition is met, the breaker moves to open. Calls that reach the breaker are rejected without invoking the protected dependency.

This changes the resource path. A locally rejected call does not need a downstream connection, does not occupy a downstream request slot, and does not wait for a remote timeout. If the caller has a fallback, stale value, deferred-work path, or explicit unavailable response, that behavior can begin immediately.

Local rejection is not equivalent to success. The application still has to represent the unavailable operation correctly. Returning fabricated success merely hides the dependency failure and can violate application invariants.

The open interval also needs to remain distinct from a rate limiter. A rate limiter constrains request admission according to a traffic policy. A breaker suppresses calls because observed outcomes indicate that the protected operation is currently unhealthy or unsuitable for continued attempts. Both may reject work, but the state transition and the evidence behind it are different.

Half-open state controls the recovery boundary

An open breaker cannot stay open forever if the dependency can recover. After a configured interval, many designs enter a half-open state and permit a limited number of probe calls.

The limit is central to the design. If every waiting request is released at once, the breaker can recreate the same pressure that existed before it opened. A half-open state instead admits a small recovery sample while other calls continue to receive local rejection.

A successful probe may move the breaker toward closed state. A failed probe may return it to open and restart the waiting interval. Implementations vary on whether one success is enough, several successes are required, or a bounded sample is evaluated. Those are policy choices rather than universal breaker semantics.

Concurrency makes this transition concrete. If multiple threads observe that the open interval has elapsed, they must not all independently decide that they own the single allowed probe. The breaker state and probe budget need synchronization or an atomic state transition appropriate to the runtime.

Breaker scope defines which failures become correlated

A breaker is useful only if its state groups calls that actually share a failure domain. One global breaker around every outbound request can couple unrelated dependencies: failure of one service could suppress calls to another healthy service.

At the opposite extreme, a breaker per request key can fragment observations so heavily that no breaker accumulates enough evidence to trip. It can also create unbounded state if keys are highly variable.

Common scopes include a remote service, a service endpoint, a host-and-port pair, or a specific operation whose failure behavior differs materially from other operations. The correct boundary follows the dependency and resource topology.

Connection pools add another dimension. Several logical operations may share one pool and therefore share saturation behavior even when their application semantics differ. A breaker scoped only by method name can miss that shared resource boundary.

Timeouts and retries remain separate controls

A circuit breaker does not replace a timeout. When the breaker is closed and a call is admitted, the caller still needs a bound on how long that attempt may occupy resources.

It also does not make retries harmless. A retry policy can multiply attempts before the breaker has enough evidence to open. If several service layers each retry independently, one user request can fan out into many downstream attempts. The breaker may eventually stop that traffic, but it cannot erase work already created by nested retry loops.

These controls need compatible budgets. The request deadline bounds total useful time. Per-attempt timeouts bound individual calls. Retry policy determines whether another attempt is justified. The breaker decides whether current dependency state permits an attempt at all.

Placing those decisions in a deliberate order makes behavior easier to reason about. A retry that encounters an open breaker should normally receive the local breaker result rather than bypass the breaker and recreate the protected call.

State transitions need observable reasons

A breaker that silently opens can make downstream traffic disappear without explaining the change. Operationally, the state transition itself is an event worth exposing.

Useful observations include current state, transition counts, rejected-call counts, probe outcomes, and the classified failures that contributed to opening. Metrics should avoid labels with unbounded cardinality, especially when breaker scope includes dynamic identifiers.

The breaker result should also remain distinguishable from a direct dependency result. A call rejected because the breaker is open did not contact the dependency. Recording it as another remote failure distorts both dependency metrics and breaker statistics.

Logs can record transitions rather than every rejected call when rejection volume is high. The transition carries more diagnostic value and avoids turning a dependency outage into a logging outage.

A breaker bounds amplification rather than guaranteeing recovery

Circuit breakers are most effective when continued calls during a failure would consume scarce resources or intensify pressure on the failing dependency. Their value comes from converting recent failure evidence into temporary local admission state.

That state has costs. A breaker can reject calls after the dependency has recovered but before the next probe. A poorly chosen scope can couple unrelated operations. A noisy threshold can oscillate between states. An overly generous half-open policy can create a recovery surge.

These are consequences of the mechanism, not reasons to treat it as a generic resilience switch. The breaker needs a failure classification, sampling policy, state scope, open interval, probe budget, and observable transition model that match the protected call path.

The core boundary is simple: once recent outcomes cross the configured failure condition, ordinary remote attempts stop for a period. Recovery traffic returns through a controlled gate. That boundary limits how aggressively one failing dependency can consume resources in the services that call it.