Cloud · Resilience · DWG NO. 025

Circuit Breaker

Stop calling a failing dependency for a cooldown period instead of retrying straight into a wall.

TypeResilience
ScopeOne dependency call
ComplexityMedium
Common inDownstream API calls, service meshes, DB clients

Retrying a dead dependency just piles up more failing requests

If a downstream service is down or overloaded, every caller that keeps hitting it with retries adds load to something already struggling, and each caller's own threads or connections sit blocked waiting on timeouts — the failure spreads instead of staying contained.

Three states: closed, open, half-open

Closed lets calls through normally, counting failures. Past a threshold, the breaker trips to open and fails fast without calling the dependency at all. After a cooldown it goes half-open, allows one trial call through, and closes again on success or re-opens on failure.

CLOSEDcalls pass throughfailures > thresholdOPENfails fastcooldown elapsesHALF-OPENone trial calltrial failstrial succeeds

A minimal circuit breaker wrapper

`call()` refuses to even attempt the request while open, and self-heals by allowing exactly one probe through once the cooldown passes.

circuit-breaker.js
class CircuitBreaker {
  #state = "closed";
  #failures = 0;
  #threshold = 3;
  #cooldownMs = 5000;
  #openedAt = 0;

  async call(fn) {
    if (this.#state === "open") {
      if (Date.now() - this.#openedAt < this.#cooldownMs) {
        throw new Error("circuit open — failing fast");
      }
      this.#state = "half-open";
    }
    try {
      const result = await fn();
      this.#failures = 0;
      this.#state = "closed";
      return result;
    } catch (err) {
      this.#failures++;
      if (this.#failures >= this.#threshold) {
        this.#state = "open";
        this.#openedAt = Date.now();
      }
      throw err;
    }
  }
}

const breaker = new CircuitBreaker();
const flakyCall = () => { throw new Error("service down"); };

for (let i = 0; i < 5; i++) {
  breaker.call(flakyCall).catch((e) => console.log(`attempt ${i}: ${e.message}`));
}

Trade-offs