FrontendPro

106Implement a circuit breaker with CLOSED, OPEN, and HALF_OPEN states

hardAbout 60 minutesCodingresiliencestate-machinesnetwork

Problem

When a dependency is down, retrying every request makes things worse: users wait for timeouts and the struggling service gets hammered. A circuit breaker fails fast instead.

Implement createCircuitBreaker(fn, { failureThreshold = 5, resetTimeout = 10000, halfOpenMaxCalls = 1, now = Date.now, onStateChange = () => {} } = {}) returning { call(...args), state }:

  • CLOSED (normal): calls go through. Count consecutive failures; a success resets the count. Reaching failureThreshold opens the circuit.
  • OPEN: every call rejects immediately with an error whose name is "CircuitOpenError", without calling fn. Once resetTimeout ms have passed since opening, the next call moves to HALF_OPEN.
  • HALF_OPEN: allow up to halfOpenMaxCalls trial calls at the same time; further calls fail fast. A successful trial closes the circuit (and resets the count); a failed trial opens it again (restarting the timeout).
  • onStateChange(from, to) is called on every transition.
const getPrices = createCircuitBreaker((ids) => api.prices(ids), { failureThreshold: 3, resetTimeout: 30000 });
try {
  render(await getPrices.call(ids));
} catch (error) {
  if (error.name === "CircuitOpenError") renderCachedPrices();
  else throw error;
}

Try it yourself

Write your solution, run it, then submit to check it against the tests.

solution.jsJavaScript

Press Run or ⌘ Enter to execute your code.

Step-by-step

Keep state, failures (consecutive), openedAt and trialsInFlight. One transition(to) function changes the state, resets what needs resetting, and calls onStateChange.

Unlock the full solution

What's inside

  • Step-by-step
  • Approach
  • Solution
  • Complexity
  • Edge cases
  • Follow-up questions
  • Tests

Includes 2 code listings, 5 edge cases, 4 follow-up questions.

One-time payment by UPI, card or net banking. Launch price.

All 150 answersOne payment, yours forever