DEV Community

Tosh
Tosh

Posted on

Circuit Breaker Pattern: Fail Fast, Recover Gracefully

Your app calls an external API. The API is down.

Your code: "I'll keep retrying forever."

User: "Your app is frozen."

Circuit breaker prevents this.

The Pattern

Imagine a circuit breaker in your house. When too much current flows, the breaker trips. Cuts the circuit. Prevents fire.

Same idea for APIs.

States:

  • CLOSED: API works, requests go through
  • OPEN: API is down, requests fail fast (no retry)
  • HALF_OPEN: API might be back, try a test request

Flow:

Request → API works → CLOSED (pass through)
Request → API fails → count failures
Failures > threshold → OPEN (fail fast, no retry)
Wait 30 seconds → HALF_OPEN (test request)
Test succeeds → CLOSED (back to normal)
Test fails → OPEN (not ready yet)
Enter fullscreen mode Exit fullscreen mode

Why This Matters

Without circuit breaker:

API is down.
Code: "Try again"
Code: "Try again"
Code: "Try again"
(30 seconds of retries)
User: "Your app is broken"
Enter fullscreen mode Exit fullscreen mode

With circuit breaker:

API is down.
Code: "Return error immediately"
User: "That service is unavailable, try later"
(Fails fast, doesn't waste time)
Enter fullscreen mode Exit fullscreen mode

Plus: circuit breaker detects when API is back online and routes traffic to it again.

Implementation (30 Minutes)

class CircuitBreaker {
  constructor(fn, threshold = 5, timeout = 30000) {
    this.fn = fn;
    this.failures = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED';
    this.nextAttempt = Date.now();
  }

  async call() {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await this.fn();
      this.onSuccess();
      return result;
    } catch (e) {
      this.onFailure();
      throw e;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failures++;
    if (this.failures >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

// Usage
const breaker = new CircuitBreaker(
  () => externalAPI.request(),
  5,  // Open after 5 failures
  30000  // Try again after 30 seconds
);

try {
  const result = await breaker.call();
} catch (e) {
  if (e.message === 'Circuit breaker OPEN') {
    return { error: 'Service unavailable, try again later' };
  }
  return { error: 'Request failed' };
}
Enter fullscreen mode Exit fullscreen mode

Real Example

Payment processing called an external payment gateway.

Gateway had occasional outages (1-2 per week, usually brief).

Without circuit breaker:

  • Outage starts
  • App tries to process payment
  • Retries 10x (30 seconds)
  • Payment fails
  • Customer angry

With circuit breaker:

  • Outage starts
  • App tries payment
  • Fails twice
  • Circuit opens
  • Next payment: instant failure with "try later"
  • 30 seconds later, circuit tries again
  • Gateway is back, circuit closes
  • Payments flow again

Same outage, but customer experience improved drastically.

Libraries

Most languages have circuit breaker libraries:

  • Node.js: opossum
  • Python: pybreaker
  • Go: gobreaker
  • Java: hystrix

Use them. Don't build from scratch.

Checklist

For every external API call:

  • [ ] Is this call potentially slow or flaky?
  • [ ] Do I have a circuit breaker?
  • [ ] What's the failure threshold? (5-10 failures)
  • [ ] What's the timeout before retry? (30 seconds)
  • [ ] Does user see helpful message on failure?

If your app hangs when external services are down, add a circuit breaker.

Fail fast. Let users know. Try again later.

Your users will thank you.

Top comments (0)