Skip to main content
Circuit Breaker Pattern: Fail Fast, Recover Faster

Circuit Breaker Pattern: Fail Fast, Recover Faster

Jul 27, 2025

Imagine an API you depend on goes down. Your app keeps trying to call it. Every request waits for timeout. Users experience 30-second hangs. Your connection pool fills up. Now your whole app is slow.

This is the cascading failure problem. Circuit breakers solve it.

The Pattern

Think of it like an electrical circuit breaker. When theres a problem, it "trips" to prevent damage:

Closed: Everything works. Requests flow through.

Open: Too many failures. Reject immediately without trying.

Half-Open: Test if service recovered. One request gets through.

Basic Implementation

class CircuitBreaker {
  private state: 'closed' | 'open' | 'half-open' = 'closed';
  private failures = 0;
  private lastFailure: number = 0;
  private readonly threshold = 5;
  private readonly timeout = 30000; // 30 seconds

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailure > this.timeout) {
        this.state = 'half-open';
      } else {
        throw new Error('Circuit breaker is open');
      }
    }

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

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

  private onFailure() {
    this.failures++;
    this.lastFailure = Date.now();

    if (this.failures >= this.threshold) {
      this.state = 'open';
    }
  }
}

Using It

const paymentBreaker = new CircuitBreaker();

async function processPayment(amount: number) {
  try {
    return await paymentBreaker.call(() =>
      paymentService.charge(amount)
    );
  } catch (error) {
    if (error.message === 'Circuit breaker is open') {
      // Fail fast - don't even try
      throw new Error('Payment service temporarily unavailable');
    }
    throw error;
  }
}

When Circuit Opens

The key insight: when the circuit is open, we fail immediately. No waiting for timeouts. No wasting resources.

Per-Service Breakers

Create separate breakers for each external service:

const breakers = {
  payment: new CircuitBreaker({ threshold: 3, timeout: 30000 }),
  inventory: new CircuitBreaker({ threshold: 5, timeout: 60000 }),
  shipping: new CircuitBreaker({ threshold: 5, timeout: 60000 }),
};

async function checkout(order: Order) {
  // Each service fails independently
  const payment = await breakers.payment.call(() =>
    paymentService.charge(order.total)
  );

  const stock = await breakers.inventory.call(() =>
    inventoryService.reserve(order.items)
  );

  // If shipping is down, payment and inventory still work
  const tracking = await breakers.shipping.call(() =>
    shippingService.create(order)
  );
}

Fallback Strategies

What to do when the circuit is open:

async function getProductPrice(productId: string) {
  try {
    return await priceBreaker.call(() =>
      priceService.getPrice(productId)
    );
  } catch (error) {
    // Fallback to cached price
    const cached = await cache.get(`price:${productId}`);
    if (cached) return cached;

    // Or return a default
    throw new Error('Price unavailable');
  }
}

Monitoring

Track circuit state for observability:

class CircuitBreaker {
  // ... existing code ...

  getStats() {
    return {
      state: this.state,
      failures: this.failures,
      lastFailure: this.lastFailure,
    };
  }
}

// Expose metrics
app.get('/health/circuits', (req, res) => {
  res.json({
    payment: breakers.payment.getStats(),
    inventory: breakers.inventory.getStats(),
    shipping: breakers.shipping.getStats(),
  });
});

Libraries

Dont reinvent the wheel for production:

Further Reading

Circuit breakers are about protecting your system from itself. When dependencies fail, fail fast and give them room to recover. Your users get quick errors instead of endless hangs, and the struggling service gets time to breathe.

© 2026 Tawan. All rights reserved.