Circuit Breaker & Back-Pressure

13 min read

Reading Progress0%
System Design Index
Start Here
Tier 1 -- Building Blocks
Tier 2 -- Core Systems
URL Shortener — System Design InterviewFree
Pastebin — System Design InterviewFree
News Feed System — System Design InterviewFree
Chat System (WhatsApp/Messenger) — System Design InterviewFree
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative
System Design Index
Start Here
Tier 1 -- Building Blocks
Tier 2 -- Core Systems
URL Shortener — System Design InterviewFree
Pastebin — System Design InterviewFree
News Feed System — System Design InterviewFree
Chat System (WhatsApp/Messenger) — System Design InterviewFree
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative

Circuit Breaker & Back-Pressure

1. What Is It?

A is a fault-tolerance pattern that wraps outbound calls to a dependency (database, external API, microservice). When the dependency starts failing at a high rate, the "trips open" — it stops forwarding calls immediately, returning errors fast rather than waiting for timeouts. This prevents the caller from accumulating blocked threads waiting on a dead dependency, which would otherwise cascade the failure upstream.

Back-pressure is a flow-control mechanism where a downstream consumer signals to an upstream producer that it's reaching capacity and the producer should slow down or stop. Instead of the consumer being overwhelmed and dropping requests, back-pressure propagates the capacity constraint upstream so the system self-regulates.

These two patterns solve related but distinct problems:

  • Circuit breaker: "The downstream service is broken — stop calling it."
  • Back-pressure: "The downstream service is too slow right now — slow the upstream producer down."

Together with the bulkhead pattern (resource isolation) and aggressive timeouts, they form the four pillars of resilience in distributed systems.


QUICK CHECK

A payment service calls an external fraud-detection API. Lately, the fraud-detection API has become completely unresponsive — every call hangs until it times out after 30 seconds. As a result, threads in the payment service pile up waiting, eventually exhausting the thread pool and causing the payment service itself to crash. Which pattern is most directly designed to prevent this failure mode?

Choose one answer

2. How It Works

Circuit Breaker: State Machine

The is modeled as a state machine with three states:

                    Failure rate >= threshold
  ┌─────────────────────────────────────────┐
  │                                         ▼
┌─────────┐   Failures accumulate   ┌──────────┐
│ CLOSED  │ ─────────────────────► │   OPEN   │
│(normal) │                         │(fail fast)│
└─────────┘                         └──────────┘
     ▲                                    │
     │    Probe calls succeed             │ After sleep window
     │                                    ▼
     │                            ┌─────────────┐
     └──────────────────────────── │  HALF-OPEN  │
                                   │(test probes) │
                                   └─────────────┘
                                         │
                                         │ Probe calls fail
                                         ▼
                                   Back to OPEN

CLOSED (normal operation):

  • All requests flow through to the dependency
  • The breaker tracks a rolling window of calls and their outcomes (success/failure/timeout)
  • When the failure rate reaches the configured threshold (default: 50% over 100 calls in Resilience4j), the breaker transitions to OPEN

OPEN (fail-fast):

  • All calls to this dependency are immediately rejected — no network call is made
  • Returns a CircuitBreakerOpenException or executes a fallback function instantly
  • After a configurable sleep window (default: 60 seconds), transitions to HALF-OPEN
  • This is the core protection: a thread that would have blocked for 10s waiting for a timeout instead fails in microseconds

HALF-OPEN (recovery testing):

  • A limited number of probe requests (default: 10) are allowed through to the dependency
  • If probe success rate ≥ threshold → breaker resets to CLOSED (dependency recovered)
  • If probe failure rate ≥ threshold → breaker returns to OPEN (still broken, wait more)

Resilience4j Configuration Example

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)           // Trip at 50% failure rate
    .slidingWindowSize(100)             // Over last 100 calls
    .waitDurationInOpenState(Duration.ofSeconds(60))  // Stay open 60s
    .permittedNumberOfCallsInHalfOpenState(10)        // 10 probe calls
    .minimumNumberOfCalls(20)           // Need at least 20 calls to calculate rate
    .recordExceptions(IOException.class, TimeoutException.class)
    .build();

CircuitBreaker circuitBreaker = CircuitBreaker.of("paymentService", config);

// Wrap the call
Supplier<Response> decoratedCall = CircuitBreaker.decorateSupplier(
    circuitBreaker,
    () -> paymentService.charge(request)
);

Try<Response> result = Try.ofSupplier(decoratedCall)
    .recover(CircuitBreakerOpenException.class, ex -> fallbackResponse());

Mermaid: Circuit Breaker in Action

Back-Pressure

Back-pressure is a feedback signal that flows upstream — the consumer tells the producer to slow down.

Without back-pressure:

Producer (fast: 10K msg/s) → Queue → Consumer (slow: 1K msg/s)
                                      Queue grows unboundedly
                                      → OOM → crash

With back-pressure:

Producer → asks: "can I send more?" → Consumer: "only request 100"
Producer sends 100 → Consumer processes 100 → signals: "ready for 100 more"
Rate matches consumer capacity — queue stays bounded

Reactive Streams protocol (request(n) pull model):

publisher.subscribe(new Subscriber<T>() {
    Subscription subscription;

    public void onSubscribe(Subscription s) {
        subscription = s;
        subscription.request(10); // "I can handle 10 items"
    }

    public void onNext(T item) {
        process(item);
        subscription.request(1); // "I've processed one, send me 1 more"
    }
});
// Publisher MUST NOT emit more items than requested

Back-pressure strategies for overflow:

StrategyWhat happens to excess itemsWhen to use
BufferHold in memory, retry laterBursty but manageable traffic; memory available
DropDiscard newest (or oldest) itemsMetrics/analytics where losing some data is acceptable
ErrorSignal onError to subscriberStrict systems where overflow means a bug
LatestKeep only most recent itemReal-time sensor data where stale values are worthless

Load Shedding vs Back-Pressure

Both address overload, but with different priorities:

Back-pressure:                      Load Shedding:
Producer ←[slow down]← Consumer    Producer → [DROP] ← Overloaded Consumer
Preserves data, increases latency   Preserves latency, loses data
First line of defense               Last resort safety valve

WHY: When a system is overwhelmed, there are exactly two choices: preserve data (accept higher ) or preserve (drop data). Back-pressure chooses data; chooses latency/. A well-designed system uses back-pressure first and only when the queue would grow unboundedly.

Bulkhead Pattern

Isolate resource pools per downstream dependency so a failure in one doesn't exhaust shared resources:

WITHOUT Bulkhead:
Shared thread pool: [===========] 100 threads
  Payment service calls blocking: 90 threads
  User service calls:               8 threads
  Inventory service calls:          2 threads
  → Payment slow → ALL services starved

WITH Bulkhead:
  Payment pool:   [=====] 30 threads (only payment calls blocked here)
  User pool:      [=====] 30 threads (unaffected)
  Inventory pool: [=====] 30 threads (unaffected)
  → Payment slow → only payment calls fail; other services unaffected

Mermaid: Resilience Patterns Together

Timeout Chains

Timeouts are the first line of defense against cascade failures:

Without timeouts:
  API Server waits 30s for DB
  → 100 concurrent requests × 30s wait = thread pool exhausted
  → New requests fail immediately
  → CASCADE

With aggressive timeouts (200ms):
  API Server waits 200ms, then returns error
  → Thread freed
  → Other requests unaffected
  → Circuit breaker eventually trips on high timeout rate
  → Cascade stopped

Timeout budgeting: Each layer in a call chain should have a shorter timeout than the layer above it:

Client timeout: 5000ms
  └─ API Gateway timeout: 3000ms
       └─ Service A timeout: 2000ms
            └─ Service B timeout: 1000ms
                 └─ Database timeout: 500ms

This ensures inner layers fail before outer layers, giving each layer a chance to handle the failure gracefully and allowing each service to provide a degraded-but-functional response.


QUICK CHECK

A payment service starts experiencing intermittent failures. Your circuit breaker trips to the OPEN state. A minute later it transitions to HALF-OPEN and sends 10 probe requests — 7 succeed and 3 fail (a 30% failure rate). Your circuit breaker is configured with a 50% failure rate threshold. What happens next?

Choose one answer

3. Variants & Comparisons

Circuit Breaker Libraries

LibraryLanguageStatusBuilt By
Resilience4jJavaActive (recommended)OSS community
HystrixJavaMaintenance mode (since Nov 2018)Netflix
Polly.NETActiveApp-vNext (OSS)
opossumNode.jsActiveOSS
pybreakerPythonActiveDaniel Grana (OSS)
EnvoyAny (sidecar)Active (service mesh)CNCF / Lyft
Istio + EnvoyAny (sidecar)ActiveCNCF

Envoy/Istio implement circuit breaking as a service mesh sidecar — language-agnostic, configured via YAML, no application code changes.

Hystrix vs Resilience4j

DimensionHystrixResilience4j
StatusMaintenance modeActively maintained
API modelHystrixCommand (imperative, annotation-heavy)Decorators (functional, lambda-based)
IsolationThread pools by defaultSemaphores by default (lighter)
MetricsRolling window (10s)Sliding window (count or time-based)
Dependency~500 classes in jarLightweight modular
Spring BootSpring Cloud Netflix (deprecated)Spring Cloud Circuit Breaker (recommended)

QUICK CHECK

Your team is migrating a Java microservices application away from a deprecated Spring Cloud Netflix stack and wants a modern circuit breaker solution. Compared to Hystrix, which characteristic of Resilience4j makes it a lighter-weight replacement?

Choose one answer

4. When to Use It (and When NOT To)

Use Circuit Breakers When:

  • Calling external services: Payment processors, SMS gateways, third-party APIs that have their own SLAs and can go down independently.
  • Microservices calling other microservices: Service A calling Service B — if B degrades, A should detect it and fail fast rather than blocking its own thread pool.
  • Database connections: Wrap DB calls in a . DB slow → trip the breaker → return cached/degraded response rather than queuing 1000 threads waiting for DB connections.
  • Any dependency with unpredictable : The is the safety net for when timeouts alone aren't enough.

Use Back-Pressure When:

  • Producer-consumer pipelines with mismatched : consumers falling behind producers; message processing queues growing unboundedly.
  • Reactive/streaming systems: Reactive Streams / RxJava / Akka Streams — back-pressure is built into the protocol.
  • with downstream rate limits: ETL pipelines, rate-limited external API calls.
  • / SSE data feeds: Server pushing real-time data to clients — clients signal how fast they can consume.

Anti-Patterns:

  • Circuit breaker with no fallback: A circuit breaker that trips open but just returns a 500 error without a fallback is better than nothing, but a cached response, default value, or "degraded mode" response is far better UX.
  • Retry without exponential backoff: Retrying on failure is correct, but retrying immediately at full rate creates a "retry storm" that hammers an already-struggling service. Always use exponential backoff (wait 1s, then 2s, then 4s, then 8s between retries) + jitter (add randomness so all clients don't retry at the same instant).
  • Shared thread pools across dependencies: Without bulkheads, one slow dependency consumes the entire thread pool. Always use separate pools or semaphores per dependency category.
  • Not accounting for circuit breaker overhead in budgets: When the circuit breaker is open, calls fail in microseconds. When half-open, some probe calls have normal latency. Your SLO calculations should account for the state.

QUICK CHECK

A backend service experiences intermittent failures when calling a payment processor API. The team adds a circuit breaker, but when the breaker trips open, the service simply returns an HTTP 500 error to the client. What improvement should the team make?

Choose one answer

5. Real-World Usage

Netflix — Hystrix at Scale

Netflix built Hystrix to manage inter-service calls across 500+ microservices. Their canonical configuration settled on 50% failure rate over a 10-second rolling window. Netflix instrumented every downstream call with a and paired it with a real-time operational dashboard (Hystrix Dashboard + Turbine for aggregating metrics across instances). Their Chaos Monkey and Chaos Kong tools actively inject failures to validate that circuit breakers actually trip and the fallback path (degraded UI without recommendations, for example) works correctly. Netflix's design goal: losing the recommendation service should degrade Netflix, not take it down.

Google SRE — Back-Pressure and Load Shedding

Google's Site Engineering book documents their approach: services automatically shed non-critical load when approaching capacity limits. Their RESOURCE_EXHAUSTED gRPC status code is the explicit back-pressure signal in their service mesh. When a backend is overloaded, it returns RESOURCE_EXHAUSTED rather than accepting work it can't complete — callers are expected to back off, reducing overall system load and allowing recovery. Google explicitly designed their systems so that overload conditions are bounded rather than unbounded collapse.

AWS SQS — Back-Pressure via Queue Depth

Amazon SQS queues naturally implement back-pressure in event-driven architectures. When consumers fall behind producers, the grows. Auto-scaling groups can use SQS as a scaling metric (via CloudWatch) — as the queue grows, more consumer instances spin up, increasing processing rate until the queue stabilizes. This is back-pressure implemented at the infrastructure layer rather than in application code.


QUICK CHECK

Your team is building an event-driven order processing system. Consumers are processing messages slower than producers are publishing them, causing a growing backlog. You want the system to automatically scale consumers in response to this pressure without writing custom application-level logic. Which infrastructure-level mechanism best addresses this?

Choose one answer

6. Interview Cheat Sheet

5 Sentences to Show Deep Understanding

  1. "A prevents cascade failures by detecting a failing dependency and fast-failing subsequent calls without attempting the network round-trip — a call that would have blocked for 10s waiting for a timeout instead fails in microseconds, freeing the thread for other work and preventing the thread pool from filling up with blocked waiters."

  2. "Back-pressure is the producer-side complement to the : instead of blocking calls to a broken downstream (circuit breaker), back-pressure slows the producer when the downstream is saturated — the consumer signals 'I can handle N more items' and the producer respects that limit rather than overwhelming the consumer."

  3. "The three states of a circuit breaker form a control loop: Closed (normal operation, tracking failure rate) → Open (fail fast, stop all calls) → Half-Open (probe with limited calls to test recovery) → Closed again if recovery confirmed. The sleep window in Open state gives the dependency time to recover before probe calls restart."

  4. " is the reason a slow payment service shouldn't take down the user profile service — by allocating separate thread pools (or semaphores) per downstream dependency, the of one dependency's slowness is contained to that dependency's pool, leaving other service calls unaffected."

  5. "Aggressive timeouts are the first line of defense: a 200ms timeout on a database call frees the thread if the DB is slow, whereas a 30s timeout holds the thread for 30 seconds — at 100 concurrent requests, 30s timeouts means thread pool exhaustion in seconds, while 200ms timeouts mean threads are recycled 150x faster."

Common Follow-Up Questions

Q: What is a retry storm and how do you prevent it? A: A retry storm occurs when a service becomes slow and multiple upstream services all start retrying simultaneously at full rate, multiplying the load on the already-struggling service. Prevention: (1) Exponential backoff with jitter — each retry waits longer, and the jitter spreads retries over time instead of synchronized waves; (2) Circuit breaker — after enough retries fail, the circuit trips open and stops retries entirely; (3) Max retry limit — don't retry forever, cap at 3-5 attempts.

Q: How do you implement a fallback when the circuit is open? A: Options in order of decreasing quality: (1) Cached response: return the last known good response (stale data is better than no data for many use cases); (2) Default/degraded response: return an empty list, a "feature unavailable" message, or a default value; (3) Alternative path: call a secondary service or a simpler backup implementation; (4) Fail gracefully: return a user-friendly error message rather than a stack trace. Which fallback to use depends on the business impact — for payments, fail loudly; for recommendations, show empty or default.

Q: What's the difference between timeout and circuit breaker? A: A timeout handles individual slow calls — it limits how long one call can block. A circuit breaker handles systemic failure patterns — it detects when many calls are timing out or erroring and proactively stops all calls rather than making each one pay the timeout penalty. Timeouts work call-by-call; circuit breakers work at the aggregate failure rate level. You need both: timeout detects the individual slow call; circuit breaker detects the pattern across many calls and stops trying.

Q: How would you tune circuit breaker thresholds in production? A: Start with Resilience4j defaults (50% failure rate, 100-call window, 60s sleep). Observe: (1) False positive rate — how often does the breaker trip when the dependency is actually healthy? Lower the threshold to reduce sensitivity. (2) Mean time to recovery — how long does the system stay degraded after a real failure? Reduce sleep window. (3) Use slow call thresholds as well (calls > 500ms count as failures) — a technically successful but very slow call is operationally a failure.

Connections to Other Building Blocks

  • Message Queues (): Back-pressure is inherent in consumer groups — the lag metric tells you when consumers are behind. Kafka provides bounded queues that absorb bursts without blocking producers.
  • Load Balancing: Load balancers and circuit breakers are complementary: the LB distributes to healthy instances (via health checks); the circuit breaker handles the case where an instance is technically healthy but its dependencies are failing.
  • : caps request rate to protect a service from too many callers. Circuit breaker stops calls to a failing dependency. Rate limiting is upstream protection; circuit breaking is downstream protection.
  • : API gateways (Kong, Envoy) implement circuit breaking as plugins — useful for protecting backend services from a failing shared dependency (e.g., all API routes share a database connection pool).
  • Strategies: Circuit breakers are especially valuable when one replica is degraded — the breaker detects high /errors from that replica and routes around it while it recovers.
Glossary History

Click dotted jargon to save explanations here.

Glossary History

Click dotted jargon to save explanations here.