All questions

Circuit Breaker

Premium

Circuit Breaker

A circuit breaker is a wrapper around an unreliable async operation that stops calling it after too many consecutive failures, then waits before cautiously letting traffic through again — shielding a struggling dependency from being hammered while it is already down. The name comes from the breaker in your home's electrical panel: when a fault appears it trips and cuts the circuit instead of letting the problem burn everything downstream.

You will implement circuitBreaker(fn, { failureThreshold, resetTimeout }). It wraps an async fn and returns two things: a call function that runs fn through the breaker, and a getState function that reports the breaker's current state. The breaker moves between three states — closed, open, and half-open — based on how fn behaves.

Signature

type State = "closed" | "open" | "half-open";

interface Options {
  failureThreshold: number; // consecutive failures allowed before tripping
  resetTimeout: number;     // ms to stay open before probing again
}

function circuitBreaker(
  fn: (...args: any[]) => Promise<any>,
  options: Options
): {
  call: (...args: any[]) => Promise<any>; // run fn through the breaker
  getState: () => State;                  // "closed" | "open" | "half-open"
};

Examples

const breaker = circuitBreaker(unreliableRequest, {
  failureThreshold: 3,
  resetTimeout: 5000,
});

// unreliableRequest is down — three rejections in a row trip the breaker:
await breaker.call().catch(() => {}); // failure 1
await breaker.call().catch(() => {}); // failure 2
await breaker.call().catch(() => {}); // failure 3 -> trips

breaker.getState();   // "open"
await breaker.call(); // rejects instantly, WITHOUT calling unreliableRequest
// After the breaker has been open for resetTimeout ms:
breaker.getState();   // "half-open" — the next call is a single probe

await breaker.call(); // probe succeeds -> breaker heals
breaker.getState();   // "closed"

// Had the probe rejected instead, the breaker would return to "open"
// and start the resetTimeout window over again.

Notes

  • Consecutive, not total — any success resets the failure count to zero. Only an unbroken run of failureThreshold failures trips the breaker.
  • Fast-fail while open — in the open state, call rejects immediately with an Error and must not invoke fn. Shielding the dependency is the entire point.
  • One probe in half-open — after resetTimeout, the next call is a single trial. A success closes the breaker; a failure re-opens it and restarts the timer.
  • Real timeresetTimeout is measured in real milliseconds; the open state ends only after that much wall-clock time has passed.
  • Assume valid inputsfn always returns a promise, and both options are positive integers. You do not need to validate arguments or support cancellation.

FAQ

What is the difference between the open and half-open states?
Open rejects every call instantly without ever touching the wrapped function. Half-open lets a single probe call through to test whether the dependency has recovered — one success closes the breaker, one failure re-opens it.
Why count consecutive failures instead of total failures?
A mostly-healthy service that fails occasionally would eventually trip a total-failure counter even though nothing is really wrong. Resetting the count on every success means the breaker only trips on a sustained run of failures, which is the real signal that a dependency is down.
How is a circuit breaker different from a retry?
A retry re-attempts the same call hoping it succeeds; a breaker stops calling a failing dependency for a cool-down window so callers fail fast instead of piling on. They are often combined — retry within the breaker.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium