Skip to content

Half-Open Probes — Complete Implementation Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Half. We cover key concepts, practical examples, and best practices to help you master this topic.

Half-open probes test whether a recovering service is ready to accept traffic, sending limited requests while the circuit is in a transitional state between open and closed.

What You'll Learn

By the end of this tutorial, you will implement half-open probe strategies, configure probe frequency and success thresholds, and handle probe failures safely.

Why It Matters

The half-open state is the most delicate part of circuit breaker design. Too aggressive probes overwhelm a recovering service. Too conservative probes delay recovery.

Real-World Use

DodaTech's circuit breakers use single-probe half-open with a 30-second reset timeout. A single successful probe closes the circuit; a single failure reopens it.

Half-Open Learning Path

flowchart LR
  A[Thresholds] --> B[Half-Open Probes]
  B --> C[Single Probe]
  B --> D[Multi-Probe]
  B --> E[Graduated]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Single Probe Strategy

The simplest half-open strategy: send one request. If it succeeds, close the circuit. If it fails, reopen.

class SingleProbeCircuitBreaker {
  constructor(options = {}) {
    this.threshold = options.threshold || 5;
    this.resetTimeout = options.resetTimeout || 30000;
    this.state = "closed";
    this.failureCount = 0;
    this.nextAttempt = Date.now();
  }

  async call(fn) {
    if (this.state === "open") {
      if (Date.now() < this.nextAttempt) {
        throw new Error("Circuit open");
      }
      this.state = "half-open";
    }

    try {
      const result = await fn();
      if (this.state === "half-open") {
        this.state = "closed";
        this.failureCount = 0;
      }
      return result;
    } catch (err) {
      this.failureCount++;
      if (this.state === "half-open" || this.failureCount >= this.threshold) {
        this.state = "open";
        this.nextAttempt = Date.now() + this.resetTimeout;
      }
      throw err;
    }
  }
}

Multi-Probe Strategy

Send multiple probe requests to get a statistically significant sample of the service's health.

class MultiProbeBreaker {
  constructor(options = {}) {
    this.probeCount = options.probeCount || 3;
    this.successThreshold = options.successThreshold || 0.66;
    this.probeTimeout = options.probeTimeout || 5000;
    this.state = "closed";
    this.failureCount = 0;
    this.nextAttempt = Date.now();
  }

  async call(fn) {
    if (this.state === "open") {
      if (Date.now() < this.nextAttempt) throw new Error("Open");
      this.state = "half-open";
    }

    if (this.state === "half-open") {
      return this.probe(fn);
    }

    return this.normalCall(fn);
  }

  async probe(fn) {
    const results = [];
    for (let i = 0; i < this.probeCount; i++) {
      try {
        const result = await Promise.race([
          fn(),
          new Promise((_, reject) =>
            setTimeout(() => reject(new Error("probe_timeout")), this.probeTimeout)
          )
        ]);
        results.push({ success: true, result });
      } catch {
        results.push({ success: false });
      }
    }

    const successes = results.filter(r => r.success).length;
    const successRate = successes / this.probeCount;

    if (successRate >= this.successThreshold) {
      this.state = "closed";
      this.failureCount = 0;
      return results.find(r => r.success).result;
    }

    this.state = "open";
    this.nextAttempt = Date.now() + this.resetTimeout;
    throw new Error(`Probe failed: ${successes}/${this.probeCount} successful`);
  }

  async normalCall(fn) {
    try {
      const result = await fn();
      return result;
    } catch (err) {
      this.failureCount++;
      if (this.failureCount >= this.threshold) {
        this.state = "open";
        this.nextAttempt = Date.now() + this.resetTimeout;
      }
      throw err;
    }
  }
}

Graduated Probe

Start with a single probe request and gradually increase probe traffic as the service shows signs of recovery.

class GraduatedProbeBreaker {
  constructor() {
    this.state = "closed";
    this.consecutiveHalfOpenFailures = 0;
    this.maxProbes = 1;
  }

  getProbeCount() {
    return Math.min(this.maxProbes, 10);
  }

  async call(fn) {
    if (this.state === "open") {
      if (Date.now() < this.nextAttempt) throw new Error("Open");
      this.state = "half-open";
    }

    if (this.state === "half-open") {
      const probes = this.getProbeCount();
      let successes = 0;

      for (let i = 0; i < probes; i++) {
        try {
          await fn();
          successes++;
        } catch {}
      }

      if (successes === probes) {
        this.state = "closed";
        this.consecutiveHalfOpenFailures = 0;
        this.maxProbes = 1;
        return;
      }

      this.consecutiveHalfOpenFailures++;
      this.maxProbes = Math.min(this.maxProbes + 1, 10);
      this.state = "open";
      this.nextAttempt = Date.now() + this.resetTimeout * Math.pow(2, this.consecutiveHalfOpenFailures);
      throw new Error(`Graduated probe: ${successes}/${probes} succeeded`);
    }

    // Normal closed handling
    try {
      return await fn();
    } catch (err) {
      this.failureCount++;
      if (this.failureCount >= 5) {
        this.state = "open";
        this.nextAttempt = Date.now() + 30000;
      }
      throw err;
    }
  }
}

Common Mistakes

  1. Sending full traffic in half-open -- The half-open state should send minimal traffic. A single probe is usually sufficient.

  2. No timeout on probes -- A hanging probe keeps the circuit half-open indefinitely. Always set probe timeouts.

  3. Ignoring probe errors -- If half-open probes fail, immediately reopen. Do not mix probe failures with the closed counter.

  4. Probing too frequently -- After a failed probe, wait at least the reset timeout before probing again.

  5. Not logging probe results -- Probe successes and failures are important signals for operations teams.

Practice Questions

  1. Why use multiple probes instead of a single probe? Multiple probes provide statistical confidence. A single probe might succeed by luck even if the service is unhealthy.

  2. What happens if a half-open probe times out? The circuit should treat a timeout as a failure and reopen immediately.

  3. How does graduated probing help recovering services? It starts with minimal load and increases gradually as the service demonstrates stability.

  4. Challenge: Implement a half-open strategy that adapts probe count based on failure history.

class AdaptiveProbeBreaker {
  getProbeCount() {
    if (this.consecutiveFailures < 2) return 1;
    if (this.consecutiveFailures < 5) return 3;
    return 5;
  }
}

FAQ

Should half-open probes use the same timeout as normal requests?

Use a shorter timeout for probes. If the service is healthy, it should respond quickly. A probe timeout of 5 seconds is reasonable.

Can half-open probes be sent to multiple instances?

Yes. Probe different instances to verify the entire service cluster is healthy, not just one instance.

How do I prevent probe storms from multiple clients?

Clients should use jittered reset timeouts so not all clients probe simultaneously.

Should the half-open state have its own failure threshold?

Yes. A single probe failure should reopen the circuit. Use a lower threshold than the closed state.

What happens if the probe succeeds but the next request fails?

The circuit closes and then immediately reopens. This is normal. Consider increasing the probe count or success threshold.

Mini Project

Build a half-open probe system with multiple strategies, configurable probe counts, and adaptive behavior.

class ProbeStrategy {
  static single() {
    return { type: "single", probeCount: 1, successThreshold: 1 };
  }

  static multi(count = 3) {
    return { type: "multi", probeCount: count, successThreshold: Math.ceil(count * 0.66) };
  }

  static graduated() {
    return { type: "graduated", initialProbes: 1, maxProbes: 10 };
  }

  static execute(strategy, fn) {
    if (strategy.type === "single") {
      return strategy.singleProbe(fn);
    }
    if (strategy.type === "multi") {
      return strategy.multiProbe(fn);
    }
    return strategy.graduatedProbe(fn);
  }

  async singleProbe(fn) {
    await fn();
  }

  async multiProbe(fn) {
    let successes = 0;
    for (let i = 0; i < this.probeCount; i++) {
      try { await fn(); successes++; } catch {}
    }
    if (successes < this.successThreshold) {
      throw new Error(`Probe failed: ${successes}/${this.probeCount}`);
    }
  }

  async graduatedProbe(fn) {
    // Implementation with increasing probe count
  }
}

What's Next

Now that you understand half-open probes, explore monitoring circuit breaker health. Then learn about using circuit breakers for HTTP calls.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro