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.
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"
};
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.
failureThreshold failures trips the breaker.open state, call rejects immediately with an Error and must not invoke fn. Shielding the dependency is the entire point.resetTimeout, the next call is a single trial. A success closes the breaker; a failure re-opens it and restarts the timer.resetTimeout is measured in real milliseconds; the open state ends only after that much wall-clock time has passed.fn always returns a promise, and both options are positive integers. You do not need to validate arguments or support cancellation.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.