Circuit Breaker Pattern

A circuit breaker temporarily stops calls to an unhealthy dependency to prevent cascading failures. It converts repeated downstream failures into fast local failures and later probes the dependency for recovery.

A circuit breaker sits between application code and a remote dependency such as a database, HTTP service, or message broker. It observes calls and tracks failures according to configured rules, such as consecutive failures or a failure ratio over a time window.

The breaker commonly has three states: `CLOSED`, `OPEN`, and `HALF_OPEN`. In `CLOSED`, calls flow normally. When the failure threshold is reached, it transitions to `OPEN`, where calls are rejected immediately rather than sent to the failing dependency.

After a configured wait period, an `OPEN` breaker can transition to `HALF_OPEN`. A small number of probe calls are then permitted. Successful probes indicate recovery and allow the breaker to return to `CLOSED`; failed probes cause it to return to `OPEN`.

A circuit breaker is not the same as load shedding. Load shedding deliberately rejects work when the system is overloaded, while a circuit breaker reacts primarily to an unhealthy dependency. In practice, both can be combined so an application protects itself from dependency failures and excessive incoming load.

For a Java backend, the implementation is usually provided by a resilience library rather than handwritten. Configuration should account for timeout behavior, failure classification, slow calls, recovery timing, fallback behavior, and observability. A breaker should not blindly count every exception: expected business errors such as validation failures generally should not trip it.

Key points

Interview questions

What problem does a circuit breaker solve?

It prevents an application from continuously calling a dependency that is failing or responding too slowly. Once failures cross a configured threshold, the breaker opens and rejects calls locally. This reduces wasted resources and helps prevent a downstream problem from spreading through the application. After a delay, it can test the dependency again.

Explain the three states of a circuit breaker.

In `CLOSED`, requests are allowed and their outcomes are monitored. When the configured failure conditions are met, the breaker changes to `OPEN` and rejects calls without contacting the dependency. After a wait period, it can enter `HALF_OPEN`, allowing a limited number of test calls. Success moves it back toward `CLOSED`, while failure normally returns it to `OPEN`.

How would you combine retries and a circuit breaker?

I would keep retries bounded and use backoff rather than repeatedly retrying immediately. The circuit breaker should prevent new traffic from reaching a dependency once its failure conditions are met. I would also use timeouts so an individual attempt cannot consume resources indefinitely. The exact ordering and scope depend on the architecture and resilience library, but the goal is to avoid multiplying load during an outage.

Which failures should a circuit breaker count?

It should generally count failures that indicate the dependency is unavailable, unhealthy, or consistently too slow according to the application's policy. It should usually exclude errors caused by invalid client input or expected business outcomes, because those do not indicate dependency health. The classification should be explicit and observable rather than treating every exception identically. The policy should also consider whether slow calls are themselves evidence of dependency degradation.

How would you design circuit breakers for a high-throughput Java service?

I would isolate breakers by dependency or meaningful dependency operation so one failing downstream system does not unnecessarily block unrelated traffic. I would combine the breaker with timeouts, bounded retries, and bulkheads, then expose metrics for state transitions, rejected calls, failure rates, and latency. Configuration should be based on observed traffic and dependency behavior rather than arbitrary thresholds. I would also make fallback semantics explicit because a fallback that returns misleading data can be worse than a fast error.