Circuit breaker simulator

A service handles 50 requests a second with 20 workers, calling a dependency that fails from second 15 to second 35. Run the same minute with and without a circuit breaker and see, second by second, how many calls fail, how many are rejected at once, and how long workers sit stuck waiting. Below it: the three states, how to tune them, and where the pattern lives in real systems.

Breaker opened
+2.2 s
Calls to the failing service
39 vs 200
The outage
Client timeout
Traffic
Chart
Requests per second, with the circuit breaker
dependency down 0 34 68 state0s15s30s45s60s
succeededfailed with an errortimed outrejected by the breakerrejected, all workers busy
closed open half-open
The same minute, with and without the breaker
WithoutWith breaker
Requests3,0623,062
Succeeded2,0261,970
Failed: error or timeout20039
Rejected by the breaker (fast)0975
Rejected: all workers busy83678
Calls sent to the failing service20039
Worker-seconds stuck during the outage38878
Mean time to an answer (ms)16054

Rejected requests count as an answer in 0 ms, which is what a fallback or an error page would give the user.

What the breaker did
  1. 17.2 sclosed → open: 50% of the last 20 calls failed.
  2. 22.2 sopen → half-open: 5 s wait over; letting 5 trial calls through.
  3. 24.4 shalf-open → open: 5 trial calls, 100% failed.
  4. 29.4 sopen → half-open: 5 s wait over; letting 5 trial calls through.
  5. 31.4 shalf-open → open: 5 trial calls, 100% failed.
  6. 36.4 sopen → half-open: 5 s wait over; letting 5 trial calls through.
  7. 36.5 shalf-open → closed: 5 trial calls, 0% failed.

Each hung call holds a worker for the full 2 s timeout, and the breaker only learns of a failure when the timeout fires, so it opened 2.2 s into the outage. Without it, 836 requests found every worker busy, including requests that did not need the failing service at all in a real system.

Breaker settings
Sliding window
last 20 calls
Minimum calls before judging
10
Failure rate to open
50%
Wait in open
5 s
Trial calls in half-open
5

How to use the simulator

The simulation runs one minute of traffic: requests arrive at random at the chosen rate, each is handled by one of 20 workers that calls a dependency, and a request that finds all workers busy is rejected. From second 15 to 35 the dependency is down, either refusing connections at once or hanging until the caller’s timeout. The same arrivals are run twice, without a breaker and with one, so the table compares like with like. The chart shows one run at a time, with the breaker’s state along the bottom.

Things to try:

  • With Hangs until timeout, chart the run Without the breaker: the workers fill up and requests are turned away for the whole outage.
  • Change the timeout from 500 ms to 5 s and watch how late the breaker opens.
  • Switch to Fails fast with errors: without a breaker the caller survives, but keeps hammering the failing service.
  • Follow the half-open trials in the log: some fail while the outage lasts, and the first set after it ends closes the breaker.

What is a circuit breaker?

A circuit breaker sits between a service and something it depends on, such as another service, a database or an external API, and watches the outcomes of calls. When failures pass a threshold, it trips: for a while, calls fail immediately inside the caller, without being sent. Then it lets a few calls through to see whether the dependency has recovered.

The name comes from the electrical device that cuts a circuit when too much current flows, protecting the wiring. Michael Nygard described the software version in Release It! (2007) as a defence against cascading failures, where one slow component drags down everything that calls it, and Martin Fowler’s 2014 article made it widely known. Netflix’s Hystrix library brought it into common use in Java microservices.

The three states

  • Closed. Calls go through normally. The breaker records each outcome in a sliding window, here the last 20 calls. Once the window holds at least the minimum number of calls (10), the breaker opens if the failure rate reaches the threshold (50%). Calls slower than a slow-call threshold can count too.
  • Open. Every call is rejected at once (Resilience4j throws CallNotPermittedException), so the caller can return an error or a fallback in microseconds. Nothing is sent to the dependency.
  • Half-open. After a wait (5 s here), the next call moves the breaker to half-open, and a fixed number of trial calls (5 here) are let through; further calls are rejected until the trials finish. If the trials’ failure rate is below the threshold the breaker closes and starts a fresh window; otherwise it opens for another wait.

These are the rules of Resilience4j’s count-based breaker, which the simulator follows; other libraries differ in detail, for example counting consecutive failures instead of a rate, or using a time-based window.

Why fail fast

A failing dependency hurts its callers most when it fails slowly. Every call to a hung service holds a thread, a connection and memory until it times out. With 20 workers, a 2-second timeout and 50 requests a second, the workers are all stuck within a fraction of a second, and from then on the service rejects everything, including requests that never needed the broken dependency. That is how one failure cascades up a call chain. In the current settings, without a breaker 388 worker-seconds were spent waiting on the dead dependency; with one, 78.

Opening the breaker turns slow failures into instant ones. Workers are freed for requests that can still succeed, users get an answer (perhaps a degraded one) immediately, and the struggling dependency stops receiving traffic it cannot handle, which often is what it needs to recover. Little’s law makes the arithmetic plain: the number of requests in flight equals the arrival rate times the time each one takes, so cutting the time per failed request from seconds to microseconds empties the queue.

Timeouts come first

A circuit breaker can only count a failure when a call ends. For a dependency that hangs, that is when the caller’s timeout fires, so the breaker cannot react sooner than the timeout plus enough calls to fill its minimum. In the model, with the dependency hanging at 50 requests a second:

Client timeoutBreaker opened afterRequests rejected for busy workers, with breakerWithout breaker
500 ms0.7 s7299
1 s1.2 s34634
2 s2.2 s78836
5 s5.2 s233948

So every remote call needs a timeout, set from the dependency’s real latency (a little above its p99 is a common choice) rather than a library default. A breaker without timeouts never sees the slow failure at all. Some libraries also count slow successful calls, so that a dependency that is degrading but not yet failing still trips the breaker.

Tuning the thresholds

The simulator uses small numbers so that things happen within a minute. Resilience4j’s documented defaults are much more conservative:

SettingResilience4j defaultIn the simulator
slidingWindowTypeCOUNT_BASEDCOUNT_BASED
slidingWindowSize10020
minimumNumberOfCalls10010
failureRateThreshold50%50%
slowCallDurationThreshold60 s1 s
slowCallRateThreshold100%100%
waitDurationInOpenState60 s5 s
permittedNumberOfCallsInHalfOpenState105

The trade-offs are the usual ones for any detector. A small window or minimum reacts quickly but trips on noise, such as a few failures in a quiet period. A large one is stable but slow. The wait in the open state trades recovery speed against how often a still-broken dependency is probed. A time-based window (the last N seconds rather than the last N calls) behaves more predictably when traffic varies a lot. Whatever the values, alert on breaker state changes: an open breaker means users are getting fallbacks.

Retries, fallbacks and bulkheads

A breaker is one of several tools that work together:

  • Retries handle brief glitches, but during an outage they multiply load: three retries per request is four times the traffic to a service that is already failing. Use exponential backoff with jitter, cap the total, and put the breaker outside the retries, or count retries as failures, so an open breaker stops them.
  • Fallbacks decide what the user gets while the breaker is open: a cached value, a default, a reduced page, or a clear error. A fallback that calls another remote service needs its own protection.
  • Bulkheads give each dependency its own pool of workers or connections, so a hung dependency can only exhaust its own pool. In the simulator all requests share 20 workers; with bulkheads, requests that did not need the failing service would have been untouched.
  • Load shedding and rate limiting protect a service from too much traffic in general, whereas a breaker reacts to a dependency’s failures.

In real systems

  • Hystrix, Netflix’s Java library, made circuit breakers, bulkheads and fallbacks standard in microservices. Netflix moved it to maintenance mode in 2018 and pointed new projects to Resilience4j.
  • Resilience4j is a lightweight Java library with circuit breakers (count- or time-based windows, as described above), retries, bulkheads, rate limiters and time limiters that can be combined.
  • Polly provides the same patterns for .NET.
  • Envoy, the proxy under Istio and many service meshes, uses “circuit breaking” for limits on concurrent connections, pending requests, requests and retries per upstream cluster, which acts like a bulkhead. The open-and-wait behaviour of the classic pattern is closer to Envoy’s outlier detection, which ejects individual hosts that return consecutive errors and readmits them after an ejection time.

The difference matters in practice: a breaker in the client treats the dependency as a whole, while outlier detection in a load balancer removes only the bad instances and keeps sending traffic to healthy ones.

When not to use one

A breaker adds state and a new failure mode: an open breaker rejects requests that might have succeeded. It is not worth it for calls to local resources that cannot fail independently, for work that must eventually happen (queue it instead and retry later), or for dependencies with so little traffic that the window never fills. And it is no substitute for timeouts, capacity planning or fixing the dependency.

Circuit breakers in interviews

Circuit breakers come up in system design interviews about microservices and reliability: “what happens to your service when the payment provider is slow?” A good answer names the three states and what moves the breaker between them, explains why slow failures are the dangerous ones (exhausted threads and connections), puts timeouts and retries with backoff alongside the breaker, and says what the user sees while it is open. Mentioning bulkheads and where the breaker runs (in the client library or in a sidecar proxy) shows depth.

Circuit breaker questions and answers

What is the circuit breaker pattern?

A circuit breaker wraps calls from one service to another. While calls succeed it stays closed and lets them through. When too many recent calls fail, it opens and rejects calls immediately for a while, without contacting the failing service. After a wait it goes half-open and lets a few trial calls through; if they succeed it closes, otherwise it opens again. The name and the pattern were popularised by Michael Nygard’s book Release It! (2007).

What are the three states of a circuit breaker?

Closed: calls go through and their outcomes are recorded. Open: calls fail immediately without being sent, until a wait duration has passed. Half-open: a limited number of trial calls are sent to test whether the dependency has recovered; their failure rate decides whether the breaker closes or opens again.

Why use a circuit breaker?

When a dependency fails slowly, every call ties up a thread, connection or memory until it times out, and the caller can run out of them and fail too, spreading the outage. A breaker turns those slow failures into instant ones, frees the caller’s resources, lets it serve a fallback, and stops sending traffic to a struggling service so it has a chance to recover.

What is the difference between a circuit breaker and a retry?

A retry repeats a failed call in the hope that the failure was brief. A circuit breaker stops calling when failures persist. They work together: retries with exponential backoff and jitter handle short glitches, and the breaker stops retries from multiplying the load on a dependency that is down. Retries should usually count toward the breaker’s failure rate.

How should I choose circuit breaker thresholds?

The window and minimum number of calls set how much evidence the breaker needs before opening: too small and it opens on random noise, too large and it reacts slowly. The failure rate threshold is commonly around 50%. The wait in the open state should be long enough for the dependency to recover but short compared with how long users will tolerate degraded service. Also set a timeout on every call, because a breaker only sees a hung call as a failure when it times out.

What does a circuit breaker do in the half-open state?

It lets a fixed number of trial calls through and rejects the rest. When the trial calls have finished, it compares their failure rate with the threshold: below it, the breaker closes and normal traffic resumes; at or above it, the breaker opens again for another wait period. Limiting the trial calls avoids hitting a recovering service with the full load at once.

Is Hystrix still maintained?

No. Netflix put Hystrix into maintenance mode in 2018 and recommended Resilience4j for new projects. Resilience4j, Polly in .NET, and service meshes and proxies such as Envoy and Istio provide circuit breaking today.

Further reading

Published Updated