Skip to content

Health Check Types — Complete Implementation Guide

DodaTech Updated 2026-06-28 7 min read

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

Health checks come in several types: basic checks verify the Process is running, dependency checks verify downstream services, deep checks verify full functionality, and Composite checks aggregate multiple health signals.

What You'll Learn

By the end of this tutorial, you will understand the five main types of health checks, when to use each, and how to combine them for comprehensive health monitoring.

Why It Matters

Different failure modes require different detection strategies. A basic health check detects crashes but not data corruption. A deep health check detects logic errors but may be too expensive to run frequently.

Real-World Use

DodaTech uses a tiered health check system: basic HTTP checks run every 5 seconds, dependency checks every 15 seconds, and deep checks every 60 seconds. Each tier has different failure thresholds.

Health Check Types Learning Path

flowchart LR
  A[Readiness vs Liveness] --> B[Health Check Types]
  B --> C[Basic Checks]
  B --> D[Dependency Checks]
  B --> E[Deep Checks]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Basic Health Check

The simplest health check returns 200 if the process is running and 503 if not.

class BasicHealthCheck {
  constructor() {
    this.startTime = Date.now();
  }

  handle(req, res) {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({
      status: "healthy",
      uptime: Math.floor((Date.now() - this.startTime) / 1000),
      timestamp: new Date().toISOString()
    }));
  }
}

const http = require("http");
const basic = new BasicHealthCheck();
const server = http.createServer((req, res) => basic.handle(req, res));
server.listen(8080);
console.log("Basic health check at /healthz");
// GET /healthz -> {"status": "healthy", "uptime": 120, "timestamp": "..."}

Dependency Health Check

Dependency checks verify that downstream services are available.

class DependencyHealthCheck {
  constructor() {
    this.dependencies = new Map();
  }

  register(name, checkFn, timeout = 2000) {
    this.dependencies.set(name, { checkFn, timeout });
  }

  async check() {
    const results = [];
    let allHealthy = true;

    for (const [name, config] of this.dependencies) {
      const start = Date.now();
      try {
        const result = await Promise.race([
          config.checkFn(),
          new Promise((_, reject) =>
            setTimeout(() => reject(new Error("timeout")), config.timeout)
          )
        ]);
        results.push({
          name,
          healthy: true,
          latencyMs: Date.now() - start
        });
      } catch (err) {
        allHealthy = false;
        results.push({
          name,
          healthy: false,
          error: err.message,
          latencyMs: Date.now() - start
        });
      }
    }

    return {
      status: allHealthy ? "healthy" : "degraded",
      dependencies: results,
      timestamp: new Date().toISOString()
    };
  }
}

async function demo() {
  const depCheck = new DependencyHealthCheck();
  depCheck.register("database", async () => {
    await new Promise(r => setTimeout(r, 100));
    return true;
  });
  depCheck.register("redis", async () => {
    await new Promise(r => setTimeout(r, 50));
    return true;
  });
  depCheck.register("external-api", async () => {
    throw new Error("Connection refused");
  });

  const result = await depCheck.check();
  console.log("Status:", result.status);
  result.dependencies.forEach(d => {
    console.log(`  ${d.name}: ${d.healthy ? "ok" : "FAIL"} (${d.latencyMs}ms)`);
  });
}

demo();
// Status: degraded
//   database: ok (100ms)
//   redis: ok (50ms)
//   external-api: FAIL (0ms)

Deep Health Check

Deep health checks verify end-to-end functionality, such as writing and reading a test record.

class DeepHealthCheck {
  constructor(db, cache) {
    this.db = db;
    this.cache = cache;
  }

  async check() {
    const checks = [];

    checks.push(this.checkDatabase());
    checks.push(this.checkCache());
    checks.push(this.checkBusinessLogic());

    const results = await Promise.allSettled(checks);
    const allHealthy = results.every(r => r.status === "fulfilled" && r.value.healthy);

    return {
      status: allHealthy ? "healthy" : "unhealthy",
      type: "deep",
      checks: results.map(r => r.status === "fulfilled" ? r.value : { healthy: false, error: r.reason.message }),
      timestamp: new Date().toISOString()
    };
  }

  async checkDatabase() {
    // Write a test record and read it back
    const testId = `health-check-${Date.now()}`;
    await this.db.set(testId, "ok");
    const value = await this.db.get(testId);
    await this.db.delete(testId);
    return { healthy: value === "ok", component: "database" };
  }

  async checkCache() {
    const testKey = `health:${Date.now()}`;
    await this.cache.set(testKey, "ok", 10);
    const value = await this.cache.get(testKey);
    return { healthy: value === "ok", component: "cache" };
  }

  async checkBusinessLogic() {
    // Verify a critical business operation works
    return { healthy: true, component: "business-logic" };
  }
}

const mockDb = { store: {}, async set(k, v) { this.store[k] = v; }, async get(k) { return this.store[k]; }, async delete(k) { delete this.store[k]; } };
const mockCache = { store: {}, async set(k, v) { this.store[k] = v; }, async get(k) { return this.store[k]; } };
const deep = new DeepHealthCheck(mockDb, mockCache);
deep.check().then(r => console.log("Deep check:", r.status));
// Deep check: healthy

Passive Health Check (Metrics-Based)

Passive checks monitor metrics like error rate and latency without active probing.

class PassiveHealthCheck {
  constructor() {
    this.metrics = {
      requests: 0,
      errors: 0,
      latencies: []
    };
    this.windowMs = 60000;
    this.windows = [];
  }

  recordRequest(latencyMs, success) {
    this.windows.push({
      time: Date.now(),
      latencyMs,
      success
    });
    this.cleanup();
  }

  cleanup() {
    const cutoff = Date.now() - this.windowMs;
    this.windows = this.windows.filter(w => w.time > cutoff);
  }

  check() {
    this.cleanup();
    const recent = this.windows;

    if (recent.length < 10) {
      return { status: "unknown", reason: "insufficient data" };
    }

    const errorRate = recent.filter(w => !w.success).length / recent.length;
    const avgLatency = recent.reduce((s, w) => s + w.latencyMs, 0) / recent.length;
    const p99Latency = recent.map(w => w.latencyMs).sort((a, b) => a - b)[Math.floor(recent.length * 0.99)];

    const unhealthy = errorRate > 0.1 || avgLatency > 5000;
    const degraded = errorRate > 0.05 || avgLatency > 2000;

    return {
      status: unhealthy ? "unhealthy" : (degraded ? "degraded" : "healthy"),
      type: "passive",
      metrics: {
        requestCount: recent.length,
        errorRate: errorRate.toFixed(3),
        avgLatencyMs: Math.round(avgLatency),
        p99LatencyMs: p99Latency
      },
      timestamp: new Date().toISOString()
    };
  }
}

const passive = new PassiveHealthCheck();
for (let i = 0; i < 100; i++) {
  passive.recordRequest(Math.random() * 1000, Math.random() > 0.02);
}
console.log("Passive check:", passive.check().status);
// Passive check: healthy

Common Mistakes

  1. Only implementing basic health checks -- A basic check only detects if the process is running. It misses dependency failures, data corruption, and degraded performance.

  2. Making deep health checks too expensive -- A deep check that makes full database queries every 5 seconds creates unnecessary load. Run deep checks less frequently.

  3. Not using passive checks -- Passive checks detect degradation that active checks might miss, like high latency but successful responses.

  4. Ignoring health check latency -- If a health check takes 10 seconds and runs every 5 seconds, it creates a backlog. Set timeouts on all health check operations.

  5. Not differentiating check types in monitoring -- Basic, dependency, and deep checks have different meanings. Track them separately in dashboards.

Practice Questions

  1. What is the difference between active and passive health checks? Active checks probe the service with explicit requests. Passive checks analyze existing traffic metrics like error rate and latency.

  2. When would you use a deep health check over a basic check? When you need to verify end-to-end functionality, such as database writes succeeding or business logic producing correct results.

  3. How do you prevent dependency health checks from cascading? Set timeouts on all dependency checks. A slow dependency should not make the health check itself slow.

  4. Challenge: Implement a tiered health checker that runs different check types at different intervals.

class TieredHealthChecker {
  constructor() {
    this.tiers = {
      basic: { interval: 5000, lastRun: 0, check: async () => ({ healthy: true }) },
      dependency: { interval: 15000, lastRun: 0, check: async () => ({ healthy: true }) },
      deep: { interval: 60000, lastRun: 0, check: async () => ({ healthy: true }) }
    };
  }

  async runDueChecks() {
    const now = Date.now();
    const results = [];

    for (const [tier, config] of Object.entries(this.tiers)) {
      if (now - config.lastRun >= config.interval) {
        console.log(`Running ${tier} check`);
        const result = await config.check();
        config.lastRun = now;
        results.push({ tier, ...result });
      }
    }

    const allHealthy = results.every(r => r.healthy);
    return { allHealthy, checks: results };
  }
}

FAQ

What is the minimum set of health checks for production?

At minimum: a basic process check and a dependency check. Add deep checks for critical services.

Should microservices check each other's health?

No. Each service checks its own dependencies. Service mesh solutions handle cross-service health.

How do you handle health checks for serverless functions?

Serverless functions are ephemeral. Use passive health monitoring based on invocation success rates instead of active probes.

Can passive health checks replace active ones?

No. Passive checks only see traffic that arrives. An idle service with no traffic appears healthy. Combine both types.

What metrics should a passive health check track?

Error rate, average latency, p99 latency, request throughput, and recent failure count. Each metric has different degradation patterns.

Mini Project

Build a multi-type health check server that supports basic, dependency, and deep checks on different endpoints, each with configurable intervals and timeouts.

const http = require("http");

class MultiTypeHealthServer {
  constructor() {
    this.checks = new Map();
  }

  register(type, checkFn) {
    this.checks.set(type, checkFn);
  }

  createServer() {
    return http.createServer(async (req, res) => {
      const url = new URL(req.url, "http://localhost");
      const type = url.pathname.replace("/health/", "");

      if (!this.checks.has(type)) {
        res.writeHead(404);
        res.end(JSON.stringify({ error: "unknown check type" }));
        return;
      }

      try {
        const result = await this.checks.get(type)();
        res.writeHead(result.healthy ? 200 : 503);
        res.end(JSON.stringify(result));
      } catch (err) {
        res.writeHead(503);
        res.end(JSON.stringify({ healthy: false, error: err.message }));
      }
    });
  }
}

const server = new MultiTypeHealthServer();
server.register("basic", async () => ({ healthy: true, uptime: process.uptime() }));
server.register("deep", async () => ({ healthy: true, db: "ok", cache: "ok" }));
server.createServer().listen(8080);

What's Next

Now that you understand health check types, implement a simple health check endpoint. Then learn about dependency health checks.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro