Health Endpoint Monitoring for Availability

intermediate 34 min read Updated 2026-02-11

TL;DR

Health endpoint monitoring exposes dedicated HTTP endpoints that external systems can probe to verify application health. Instead of waiting for failures to manifest as user-facing errors, monitoring systems actively check these endpoints at regular intervals (typically 10-60 seconds) to detect degraded states, dependency failures, or resource exhaustion before they cascade into outages. This pattern is the foundation of proactive availability management in distributed systems.

Cheat Sheet: Expose /health endpoints that check critical dependencies (database, cache, downstream services). Return 200 for healthy, 503 for unhealthy. Use shallow checks (< 1s) for liveness, deep checks for readiness. Monitor from multiple geographic locations to detect regional failures.

The Analogy

Think of health endpoints like the diagnostic port on your car that mechanics plug into during inspections. Your car doesn’t wait until the engine seizes to tell you there’s a problem—the diagnostic system continuously monitors oil pressure, coolant temperature, and sensor readings. When you bring it in for service, the mechanic gets instant visibility into what’s working and what’s degraded. Similarly, health endpoints let monitoring systems “plug in” to your application and get real-time status without waiting for catastrophic failure. Just as a car’s check engine light can indicate anything from a loose gas cap to engine failure, health endpoints can report varying levels of degradation, allowing operators to intervene before users are affected.

Why This Matters in Interviews

Health endpoint monitoring comes up in nearly every system design interview when discussing availability, observability, or deployment strategies. Interviewers use this topic to assess whether you understand the difference between reactive monitoring (waiting for failures) and proactive monitoring (detecting issues early). They’re looking for candidates who can design endpoints that provide actionable information without creating new failure modes. Mid-level engineers should explain basic health checks; senior engineers should discuss the nuances of deep vs. shallow checks, graceful degradation, and integration with load balancers; staff+ engineers should architect comprehensive health monitoring strategies that balance observability with system stability, including considerations for multi-region deployments and cascading failure prevention.


Core Concept

Health endpoint monitoring is a reliability pattern where applications expose dedicated HTTP endpoints that external monitoring systems can query to determine operational status. Unlike passive monitoring that waits for errors to occur, health endpoints enable active probing—monitoring systems make regular HTTP requests to these endpoints and interpret the responses to assess whether the application is functioning correctly. This pattern emerged from the need to detect failures faster than users could report them, particularly in distributed systems where a single application might depend on dozens of downstream services.

The pattern addresses a fundamental challenge in modern infrastructure: how do you know if an application is truly healthy? Traditional approaches like checking if a process is running or if a port is open are insufficient—an application can be running but unable to serve requests due to database connection pool exhaustion, memory leaks, or downstream service failures. Health endpoints solve this by letting the application itself report its status based on internal checks of critical dependencies and resources. This self-reporting approach provides much richer information than external observation alone.

Health endpoint monitoring integrates with multiple infrastructure layers. Load balancers use health checks to route traffic only to healthy instances. Container orchestrators like Kubernetes use liveness and readiness probes to restart failed containers or delay traffic routing. Monitoring systems use health endpoints to trigger alerts and automated remediation. This multi-layered integration makes health endpoints a critical component of high-availability architectures, enabling systems to automatically detect and respond to failures without human intervention.

How It Works

Step 1: Endpoint Definition and Registration The application exposes one or more HTTP endpoints dedicated to health reporting, typically at paths like /health, /healthz, or /ready. These endpoints are registered with the application’s HTTP router but are intentionally kept separate from business logic endpoints. The endpoints accept GET requests and return HTTP status codes (200 for healthy, 503 for unhealthy) along with optional JSON payloads containing detailed status information. Most implementations expose multiple endpoints for different purposes: a shallow liveness endpoint that confirms the process is running, and deeper readiness endpoints that verify the application can actually serve traffic.

Step 2: Internal Health Assessment When a health endpoint receives a request, it executes a series of checks against critical dependencies and resources. For a typical web application, this might include: verifying database connectivity by executing a simple query, checking that cache connections are established, confirming that message queues are reachable, and validating that critical configuration is loaded. Each check has a timeout (typically 100-500ms) to prevent the health check itself from hanging. The application aggregates results from all checks to determine overall health status. Some implementations use weighted scoring where certain dependencies are marked as critical (failure means unhealthy) versus optional (failure means degraded but still operational).

Step 3: Response Generation Based on the aggregated health status, the endpoint returns an appropriate HTTP response. A healthy system returns 200 OK with a JSON payload detailing which checks passed. An unhealthy system returns 503 Service Unavailable with information about which checks failed. Some implementations include additional status codes like 429 Too Many Requests if the system is overloaded, or custom codes for degraded states. The response payload typically includes timestamps, check durations, and dependency-specific details. This information helps operators diagnose issues quickly without needing to SSH into servers or parse application logs.

Step 4: External Monitoring and Action Monitoring systems poll health endpoints at regular intervals (10-60 seconds is typical). When a health check fails, the monitoring system takes action based on the failure type and duration. Load balancers immediately stop routing new requests to unhealthy instances while allowing existing connections to drain. Container orchestrators may restart containers that fail liveness checks or delay traffic routing to containers that fail readiness checks. Alerting systems notify on-call engineers when failures persist beyond a threshold. Some sophisticated setups use health check results to trigger automated remediation like scaling up capacity, failing over to backup regions, or executing runbooks that attempt common fixes.

Step 5: Continuous Monitoring and Trend Analysis Beyond binary healthy/unhealthy decisions, monitoring systems track health check metrics over time. They measure response latency trends (increasing latency often precedes failures), check success rates, and patterns in which specific dependencies fail. This historical data enables predictive alerting—for example, if database connection pool utilization has been climbing steadily, operators can add capacity before the pool exhausts and health checks start failing. The continuous monitoring also helps identify intermittent issues that might not trigger immediate alerts but indicate underlying problems, such as network flakiness or resource contention during peak traffic periods.

Health Check Request Flow with Load Balancer Integration

graph LR
    LB["Load Balancer"]
    Monitor["Monitoring System"]
    
    subgraph Application Instance
        HTTP["HTTP Router"]
        HealthEndpoint["/health Endpoint"]
        
        subgraph Health Checks
            DBCheck["Database Check<br/><i>SELECT 1</i>"]
            CacheCheck["Cache Check<br/><i>GET test-key</i>"]
            CBCheck["Circuit Breaker<br/>State Check"]
        end
        
        Aggregator["Result Aggregator"]
        Response["HTTP Response<br/><i>200 or 503</i>"]
    end
    
    DB[("Database")]
    Cache[("Redis Cache")]
    API["Downstream API"]
    
    Monitor --"1. GET /health<br/>every 15s"--> HTTP
    LB --"2. GET /health<br/>every 10s"--> HTTP
    HTTP --"3. Route to handler"--> HealthEndpoint
    HealthEndpoint --"4. Execute checks<br/>(parallel)"--> DBCheck
    HealthEndpoint --"4. Execute checks<br/>(parallel)"--> CacheCheck
    HealthEndpoint --"4. Execute checks<br/>(parallel)"--> CBCheck
    
    DBCheck --"5. Query<br/>(100ms timeout)"--> DB
    CacheCheck --"5. GET<br/>(100ms timeout)"--> Cache
    CBCheck --"5. Check state<br/>(no external call)"--> API
    
    DB --"6. Response"--> DBCheck
    Cache --"6. Response"--> CacheCheck
    API -."Circuit breaker<br/>prevents call".-> CBCheck
    
    DBCheck & CacheCheck & CBCheck --"7. Check results"--> Aggregator
    Aggregator --"8. Generate response"--> Response
    Response --"9. Return status"--> HTTP
    HTTP --"10. 200 OK or 503"--> Monitor
    HTTP --"10. 200 OK or 503"--> LB
    
    LB --"11. Route traffic only<br/>to healthy instances"--> HTTP

Complete health check flow showing how monitoring systems and load balancers poll the /health endpoint, which executes parallel dependency checks with timeouts. The circuit breaker check examines state without making external calls, preventing additional load on struggling services. Results are aggregated and returned as HTTP status codes that load balancers use for traffic routing decisions.

Key Principles

Principle 1: Separate Liveness from Readiness Liveness checks answer “Is the process alive?” while readiness checks answer “Can the process serve traffic?” This distinction is critical for preventing cascading failures. A liveness check should be extremely lightweight—often just returning 200 OK without any dependency checks—because failing a liveness check typically results in the process being killed and restarted. Readiness checks can be more thorough, verifying that databases are reachable and caches are warmed up, because failing readiness just means the instance won’t receive new traffic while it recovers. Kubernetes codified this distinction with separate livenessProbe and readinessProbe configurations. For example, a newly started application might fail readiness checks while loading data into memory but should pass liveness checks because the process itself is healthy.

Principle 2: Health Checks Must Be Fast and Bounded A health check endpoint that takes 10 seconds to respond is worse than no health check at all—it creates a new failure mode where slow health checks cause cascading timeouts in monitoring systems. Every dependency check within a health endpoint must have strict timeouts (typically 100-500ms). The total health check response time should never exceed 1-2 seconds. This means you can’t run comprehensive system tests in health checks; instead, focus on quick connectivity checks like opening a database connection or pinging a cache. Netflix’s approach is instructive: their health checks execute simple queries like SELECT 1 against databases rather than complex business logic queries. Fast health checks enable frequent polling without overwhelming the system.

Principle 3: Avoid Creating New Failure Modes Ironically, poorly designed health checks can cause the outages they’re meant to prevent. If every health check opens a new database connection without proper pooling, frequent health check polling can exhaust connection pools and make the application unhealthy. If health checks call downstream services synchronously, a slow downstream service can make your health checks timeout, causing your load balancer to mark you unhealthy even though you could serve cached responses. The solution is to make health checks observe existing state rather than create new work. Check if a connection pool has available connections rather than opening a new connection. Verify that a circuit breaker to a downstream service is closed rather than making an actual call. This observational approach ensures health checks remain lightweight and don’t amplify problems.

Principle 4: Return Actionable Information A health check that simply returns “unhealthy” forces operators to dig through logs to understand what’s wrong. Good health endpoints return structured information about which specific checks failed and why. For example: {"status": "unhealthy", "checks": {"database": {"status": "fail", "error": "connection timeout after 500ms", "lastSuccess": "2024-01-15T10:23:45Z"}, "cache": {"status": "pass"}}}. This detailed response lets operators immediately identify that the database is the problem and see when it last worked. Some teams include remediation hints in health check responses, like “Consider checking database connection pool settings” for connection exhaustion errors. The goal is to minimize time-to-diagnosis when incidents occur.

Principle 5: Design for Graceful Degradation Not all dependency failures should mark an application as completely unhealthy. A recommendation service that can’t reach its machine learning model might still serve cached recommendations. A payment service that can’t reach a fraud detection API might still process low-value transactions. Health endpoints should support partial health states that indicate degraded but operational status. This requires categorizing dependencies as critical (failure means unhealthy) versus optional (failure means degraded). Load balancers can then make nuanced decisions—perhaps routing traffic to degraded instances only when all healthy instances are at capacity. This graceful degradation approach maximizes availability by keeping partially functional instances in service rather than taking them offline completely.

Liveness vs. Readiness Check Comparison

graph TB
    subgraph Liveness Check Purpose
        L1["Question: Is the process alive?"]
        L2["Check: HTTP responds"]
        L3["No dependency checks"]
        L4["Response time: <50ms"]
        L5["Failure action: Restart container"]
    end
    
    subgraph Readiness Check Purpose
        R1["Question: Can it serve traffic?"]
        R2["Check: Dependencies healthy"]
        R3["Verify DB, cache, APIs"]
        R4["Response time: <1s"]
        R5["Failure action: Remove from LB pool"]
    end
    
    subgraph Example Scenario: Database Connection Pool Exhausted
        S1["Liveness: PASS<br/><i>Process is running</i>"]
        S2["Readiness: FAIL<br/><i>Can't get DB connection</i>"]
        S3["Result: Instance stays alive<br/>but receives no traffic"]
        S4["Benefit: Avoids restart loop<br/>that would worsen DB load"]
    end
    
    L1 --> L2 --> L3 --> L4 --> L5
    R1 --> R2 --> R3 --> R4 --> R5
    
    L5 -."Different actions".-> R5
    
    S1 & S2 --> S3 --> S4

Liveness checks detect completely dead processes requiring restart, while readiness checks verify the ability to serve traffic. The distinction prevents restart loops during transient dependency issues—a process with exhausted database connections should be removed from rotation (readiness failure) but not restarted (liveness passes), allowing the database to recover without additional connection attempts.


Deep Dive

Types / Variants

Shallow Liveness Checks Shallow liveness checks verify only that the application process is running and can respond to HTTP requests. They typically return 200 OK immediately without checking any dependencies. These checks are used by container orchestrators to detect completely dead processes that need restarting. The implementation is trivial—often just a handler that returns a static response. Use shallow liveness checks when you want to detect process crashes or hangs but don’t want to risk false positives from transient dependency issues. The main advantage is speed and reliability—they never fail unless the process is truly dead. The disadvantage is they provide no information about whether the application can actually do useful work. Kubernetes uses this pattern for its livenessProbe, which defaults to restarting containers that fail liveness checks three times in a row.

Deep Readiness Checks Deep readiness checks verify that the application can serve traffic by testing critical dependencies like databases, caches, message queues, and downstream services. These checks are more comprehensive but also slower and more prone to false positives. A typical implementation might check database connectivity with SELECT 1, verify cache availability with a GET operation, and confirm message queue connectivity. Use deep readiness checks when you want to prevent routing traffic to instances that can’t fulfill requests. The advantage is catching real problems before they affect users—an instance with a broken database connection won’t receive traffic. The disadvantage is that transient network issues or slow dependencies can cause false positives that unnecessarily remove healthy instances from load balancer pools. Google’s Site Reliability Engineering book recommends keeping readiness checks under 1 second total to balance thoroughness with responsiveness.

Startup Probes Startup probes are specialized health checks used during application initialization to determine when an instance is ready to receive traffic for the first time. They’re particularly important for applications with long startup times—for example, applications that need to load large datasets into memory, warm up JIT compilers, or establish connections to dozens of downstream services. Startup probes typically have longer timeouts (30-120 seconds) and check more thoroughly than ongoing readiness checks. Use startup probes when your application has a distinct initialization phase that’s different from steady-state operation. The advantage is preventing premature traffic routing to instances that aren’t fully initialized. The disadvantage is complexity—you need to maintain separate logic for startup versus ongoing health. Kubernetes introduced startupProbe in version 1.16 specifically to handle this use case, allowing longer timeouts during startup without affecting ongoing liveness check intervals.

Dependency-Specific Health Endpoints Some architectures expose separate health endpoints for different dependency categories: /health/database, /health/cache, /health/downstream-services. This granular approach allows monitoring systems to understand exactly which dependencies are failing and potentially take targeted remediation actions. For example, if only the cache is unhealthy, the monitoring system might trigger cache warming rather than restarting the entire application. Use dependency-specific endpoints in complex applications with many dependencies where you want fine-grained observability. The advantage is precise diagnosis and targeted remediation. The disadvantage is complexity—you need to maintain multiple endpoints and monitoring systems need to understand what each endpoint means. Twitter’s infrastructure uses this pattern extensively, with separate health checks for each major subsystem.

Aggregated Health Endpoints Aggregated health endpoints check multiple dependencies and return a single overall health status with detailed breakdown in the response payload. This is the most common pattern, balancing simplicity with informativeness. A typical response might be: {"status": "healthy", "checks": {"database": "pass", "cache": "pass", "api": "pass"}, "duration_ms": 234}. Use aggregated endpoints when you want a single integration point for monitoring systems but still need detailed diagnostic information. The advantage is simplicity for consumers—one endpoint to poll, one status to interpret. The disadvantage is that you need to decide how to aggregate results (is one failing check enough to mark the whole system unhealthy?). Most production systems use this pattern because it provides the best balance of usability and information density.

Passive Health Checks Passive health checks don’t involve dedicated health endpoints. Instead, load balancers and proxies monitor actual traffic to determine health—if an instance returns too many 5xx errors or connection timeouts, it’s marked unhealthy. This approach is used by systems like NGINX Plus and HAProxy. Use passive health checks when you want to minimize overhead (no additional health check traffic) or when you can’t modify applications to add health endpoints. The advantage is zero application code required and detection based on real user traffic patterns. The disadvantage is slower detection—you need to wait for enough failed requests to accumulate before marking an instance unhealthy, which means some users will experience errors. Passive checks work best in combination with active checks, providing a second layer of validation based on actual behavior rather than self-reported status.

Health Check Types and Their Use Cases

graph TB
    subgraph Shallow Liveness
        SL1["Implementation:<br/>Return 200 OK immediately"]
        SL2["Use case:<br/>Kubernetes livenessProbe"]
        SL3["Detects: Process crashes,<br/>deadlocks, hangs"]
        SL4["Speed: <10ms"]
    end
    
    subgraph Deep Readiness
        DR1["Implementation:<br/>Check DB, cache, APIs"]
        DR2["Use case:<br/>Load balancer routing"]
        DR3["Detects: Dependency failures,<br/>resource exhaustion"]
        DR4["Speed: 200-1000ms"]
    end
    
    subgraph Startup Probe
        SP1["Implementation:<br/>Comprehensive initialization checks"]
        SP2["Use case:<br/>Slow-starting applications"]
        SP3["Detects: Incomplete warmup,<br/>missing configuration"]
        SP4["Speed: 5-30s (one-time)"]
    end
    
    subgraph Passive Health Check
        PH1["Implementation:<br/>Monitor actual traffic errors"]
        PH2["Use case:<br/>NGINX, HAProxy"]
        PH3["Detects: Real user-facing<br/>failures"]
        PH4["Speed: N/A (observational)"]
    end
    
    App["Application Lifecycle"]
    
    App --"1. Starting"--> SP1
    SP1 --> SP2 --> SP3 --> SP4
    
    App --"2. Running"--> SL1
    SL1 --> SL2 --> SL3 --> SL4
    
    App --"3. Serving Traffic"--> DR1
    DR1 --> DR2 --> DR3 --> DR4
    
    App --"4. Under Load"--> PH1
    PH1 --> PH2 --> PH3 --> PH4

Different health check types serve distinct purposes across the application lifecycle. Startup probes handle slow initialization with longer timeouts, liveness probes detect dead processes requiring restart, readiness probes verify traffic-serving capability, and passive checks observe actual traffic patterns. Using the appropriate check type for each lifecycle stage prevents false positives and enables targeted remediation.

Trade-offs

Check Depth: Shallow vs. Deep Shallow checks verify only that the process is alive and can respond to HTTP requests. Deep checks verify that all critical dependencies are reachable and functional. The tradeoff is speed and reliability versus thoroughness. Shallow checks respond in milliseconds and rarely produce false positives, but they can’t detect dependency failures—your application might be marked healthy while unable to reach its database. Deep checks catch real problems but take longer (hundreds of milliseconds) and can produce false positives from transient network issues. The decision framework: use shallow checks for liveness probes where false positives would cause unnecessary restarts, and deep checks for readiness probes where false positives just temporarily remove an instance from rotation. In practice, most systems use both—shallow liveness checks every 10 seconds and deeper readiness checks every 30 seconds.

Check Frequency: Aggressive vs. Conservative Aggressive checking polls health endpoints every 5-10 seconds, enabling fast failure detection. Conservative checking polls every 60-120 seconds, reducing overhead. The tradeoff is detection speed versus system load. Aggressive checking detects failures within seconds, minimizing user impact, but generates significant monitoring traffic—1000 instances checked every 10 seconds means 100 requests/second just for health checks. This traffic can overwhelm monitoring systems and create load on the applications being monitored. Conservative checking reduces overhead but means failures might go undetected for minutes. The decision framework: use aggressive checking for user-facing services where every second of downtime matters, and conservative checking for background workers or internal services where slower detection is acceptable. Netflix uses 10-second intervals for API services but 60-second intervals for batch processing jobs.

Failure Threshold: Sensitive vs. Tolerant Sensitive thresholds mark instances unhealthy after a single failed check. Tolerant thresholds require multiple consecutive failures (typically 3-5) before marking unhealthy. The tradeoff is false positive rate versus detection speed. Sensitive thresholds catch problems immediately but can cause flapping—instances rapidly marked unhealthy and healthy due to transient issues, causing unnecessary traffic shifts. Tolerant thresholds reduce flapping but mean instances might serve errors for 30-60 seconds before being removed from rotation. The decision framework: use sensitive thresholds when health checks are highly reliable and false positives are rare, and tolerant thresholds when network conditions are variable or dependencies occasionally have brief hiccups. Most production systems use tolerant thresholds (3 consecutive failures) to balance responsiveness with stability. Kubernetes defaults to 3 failures for liveness probes and 3 for readiness probes.

Response Detail: Minimal vs. Verbose Minimal responses return only HTTP status codes (200 or 503). Verbose responses include detailed JSON payloads with per-dependency status, error messages, timestamps, and metrics. The tradeoff is simplicity versus debuggability. Minimal responses are fast to generate and easy for monitoring systems to parse, but provide no diagnostic information—when something fails, operators must dig through logs to understand why. Verbose responses enable rapid diagnosis but increase response size (potentially hundreds of bytes vs. zero bytes) and expose internal system details that might be security-sensitive. The decision framework: use minimal responses for high-frequency liveness checks where speed matters most, and verbose responses for readiness checks and manual debugging. Many systems use content negotiation—returning minimal responses by default but verbose responses when requested with specific headers. Stripe’s health endpoints return detailed JSON only when called with Accept: application/json.

Dependency Handling: Fail-Fast vs. Graceful Degradation Fail-fast approaches mark the entire application unhealthy if any critical dependency fails. Graceful degradation approaches categorize dependencies as critical vs. optional and return degraded status when optional dependencies fail. The tradeoff is simplicity versus availability. Fail-fast is easier to implement and reason about—either everything works or nothing works—but reduces availability because the application is removed from service even when it could handle some requests. Graceful degradation maximizes availability by keeping partially functional instances in service, but requires complex logic to determine which operations are possible without each dependency. The decision framework: use fail-fast for tightly coupled systems where dependency failures make the application genuinely unusable, and graceful degradation for loosely coupled systems where many operations can succeed independently. Amazon’s retail services use graceful degradation extensively—if the recommendation engine fails, the site still serves product pages, just without personalized recommendations.

Graceful Degradation vs. Fail-Fast Health Check Strategy

graph LR
    subgraph Fail-Fast Approach
        FF1["Any critical dependency fails"]
        FF2["Health check returns 503"]
        FF3["Load balancer removes instance"]
        FF4["Result: Binary healthy/unhealthy"]
        FF5["Pro: Simple to implement<br/>Con: Reduces availability"]
    end
    
    subgraph Graceful Degradation Approach
        GD1["Categorize dependencies:<br/>Critical | Important | Optional"]
        GD2["Critical fails: 503<br/>Important fails: 200 + degraded<br/>Optional fails: 200"]
        GD3["Load balancer keeps instance<br/>but prefers healthy ones"]
        GD4["Result: Graduated health status"]
        GD5["Pro: Maximizes availability<br/>Con: Complex implementation"]
    end
    
    Scenario["Scenario:<br/>Recommendation API down"]
    
    Scenario --"Fail-Fast"--> FF1
    FF1 --> FF2 --> FF3 --> FF4 --> FF5
    
    Scenario --"Graceful Degradation"--> GD1
    GD1 --> GD2 --> GD3 --> GD4 --> GD5
    
    FF_Result["Outcome: Site completely offline<br/>even though product pages work"]
    GD_Result["Outcome: Site serves product pages<br/>without personalized recommendations"]
    
    FF5 --> FF_Result
    GD5 --> GD_Result

Fail-fast marks the entire service unhealthy when any critical dependency fails, maximizing simplicity but reducing availability. Graceful degradation categorizes dependencies by criticality and returns graduated health status, keeping partially functional instances in service. The choice depends on whether your service can meaningfully operate with degraded functionality—e-commerce sites benefit from graceful degradation (serve product pages without recommendations), while payment processors typically use fail-fast (can’t process payments without fraud detection).


Math & Calculations

Health Check Overhead Calculation

When designing health check intervals, you need to calculate the monitoring overhead to ensure health checks don’t become a significant load on your system.

Formula: Requests per second = (Number of instances × Number of monitoring locations) / Check interval in seconds

Variables:

  • Number of instances: How many application instances you’re running
  • Number of monitoring locations: How many independent monitors are checking each instance (for redundancy)
  • Check interval: How often each monitor checks each instance

Worked Example: You’re running a microservice with 500 instances across 3 regions. You have 2 monitoring systems (for redundancy) checking from 3 geographic locations each (6 total monitoring locations). You want to check every 10 seconds.

Health check requests/sec = (500 instances × 6 monitoring locations) / 10 seconds = 300 requests/second

If each health check takes 100ms of CPU time and returns 500 bytes, you’re consuming:

  • CPU: 300 requests/sec × 100ms = 30 seconds of CPU per second = 30 CPU cores just for health checks
  • Bandwidth: 300 requests/sec × 500 bytes = 150 KB/sec = 13 GB/day

This example shows why health check efficiency matters at scale. If you reduced the check interval to 5 seconds (for faster failure detection), you’d double the overhead to 600 requests/second and 60 CPU cores. If you increased to 30 seconds, you’d reduce overhead to 100 requests/second and 10 CPU cores, but failures would take 30-90 seconds to detect (accounting for failure thresholds).

Failure Detection Time Calculation

Formula: Maximum detection time = (Check interval × Failure threshold) + Check timeout

Variables:

  • Check interval: Time between health checks
  • Failure threshold: Number of consecutive failures required
  • Check timeout: Maximum time to wait for a health check response

Worked Example: Your monitoring system checks every 15 seconds, requires 3 consecutive failures, and has a 2-second timeout.

Maximum detection time = (15 seconds × 3 failures) + 2 seconds = 47 seconds

This means in the worst case, a failure occurring just after a successful health check won’t be detected for 47 seconds. During this time, the failed instance might receive traffic and return errors to users. If your SLA requires detecting failures within 30 seconds, you need to either reduce the check interval (to 10 seconds: 10×3+2=32 seconds) or reduce the failure threshold (to 2 failures: 15×2+2=32 seconds).

Availability Impact Calculation

Health checks directly impact your availability SLA by determining how quickly you detect and route around failures.

Formula: Availability = (Total time - Undetected failure time) / Total time

If your system has a 99.9% availability target (43.2 minutes of downtime per month allowed) and your health checks take 45 seconds to detect failures, each failure consumes 45 seconds of your downtime budget. If you have 20 instance failures per month (common in large deployments), that’s 15 minutes of downtime just from detection delay, leaving only 28.2 minutes for other issues. Reducing detection time to 15 seconds would cut this to 5 minutes, giving you much more budget for other problems.


Real-World Examples

Netflix: Multi-Tiered Health Checks with Eureka

Netflix’s Eureka service discovery system implements sophisticated health checking across their microservices architecture. Each service instance registers with Eureka and sends heartbeats every 30 seconds—these are lightweight liveness checks that simply confirm the instance is alive. Separately, Eureka performs application-level health checks by calling each service’s /health endpoint, which verifies critical dependencies like databases and downstream services. The interesting detail is Netflix’s use of “traffic shaping” based on health status: instances don’t just flip between healthy and unhealthy, they report graduated health scores (0-100). An instance at 50% health receives half the normal traffic, allowing it to recover gradually rather than being completely removed from rotation. This approach prevented cascading failures during a major incident where database connection pools were slowly exhausting—instead of instances suddenly going completely offline, they gradually reduced their traffic intake, giving the database time to recover. Netflix also implements client-side health checking where calling services maintain their own health scores for downstream dependencies, enabling faster failure detection than centralized monitoring alone.

Kubernetes: Liveness, Readiness, and Startup Probes

Kubernetes codified health endpoint monitoring into its core architecture with three distinct probe types. Liveness probes determine if a container should be restarted—they’re designed to catch deadlocks or hung processes that can’t recover without a restart. Readiness probes determine if a container should receive traffic—they’re used during rolling deployments to ensure new containers are fully initialized before receiving production traffic. Startup probes handle the special case of slow-starting containers, allowing longer timeouts during initialization without affecting ongoing liveness check intervals. The interesting detail is how Kubernetes handles probe failures: failed liveness probes trigger container restarts with exponential backoff (preventing restart loops), while failed readiness probes simply remove the pod from service endpoints without restarting it. This distinction prevents a common anti-pattern where transient dependency issues cause unnecessary restarts that make problems worse. Google’s internal systems (Borg, Kubernetes’ predecessor) learned this lesson after incidents where aggressive liveness probe restarts created cascading failures—a database hiccup would cause application restarts, which would create a thundering herd of new connections, overwhelming the database further.

Stripe: Dependency-Aware Health Checks

Stripe’s payment processing infrastructure uses health checks that understand dependency criticality and implement graceful degradation. Their API services expose a /healthz endpoint that checks multiple dependencies but categorizes them as critical (database, payment processor connections) versus optional (fraud detection, analytics). When optional dependencies fail, the health endpoint returns 200 OK with a degraded status indicator in the JSON payload, and the service continues processing payments with reduced functionality (e.g., skipping real-time fraud scoring and using cached risk models instead). The interesting detail is Stripe’s use of “health check budgets”—each dependency check has a time budget (typically 100ms), and if a check exceeds its budget, it’s automatically failed to prevent slow dependencies from making health checks timeout. They also implement “check result caching” where dependency health is cached for 5-10 seconds, so multiple health check requests within that window return cached results rather than re-checking dependencies. This prevents health check traffic from overwhelming dependencies during incidents when monitoring systems might be polling more aggressively. During a major database incident, this caching prevented health checks from adding additional load to an already struggling database, allowing it to recover faster.

Kubernetes Multi-Probe Architecture

graph TB
    subgraph Pod Lifecycle
        Start["Container Starting"]
        Init["Initialization Phase"]
        Ready["Ready for Traffic"]
        Running["Serving Requests"]
        Failed["Container Failed"]
    end
    
    subgraph Startup Probe
        SP["startupProbe<br/><i>initialDelaySeconds: 0<br/>periodSeconds: 10<br/>failureThreshold: 30</i>"]
        SP_Check["Check: App initialized?<br/>Timeout: 5s"]
        SP_Pass["Pass: Enable other probes"]
        SP_Fail["Fail after 300s:<br/>Kill container"]
    end
    
    subgraph Liveness Probe
        LP["livenessProbe<br/><i>periodSeconds: 10<br/>failureThreshold: 3</i>"]
        LP_Check["Check: Process alive?<br/>Timeout: 1s"]
        LP_Pass["Pass: Continue running"]
        LP_Fail["Fail 3x: Restart container"]
    end
    
    subgraph Readiness Probe
        RP["readinessProbe<br/><i>periodSeconds: 5<br/>failureThreshold: 3</i>"]
        RP_Check["Check: Dependencies healthy?<br/>Timeout: 1s"]
        RP_Pass["Pass: Add to Service endpoints"]
        RP_Fail["Fail 3x: Remove from endpoints"]
    end
    
    Start --> Init
    Init --"Startup probe active"--> SP
    SP --> SP_Check
    SP_Check --"Success"--> SP_Pass
    SP_Check --"Timeout"--> SP_Fail
    SP_Pass --> Ready
    SP_Fail --> Failed
    
    Ready --"Liveness probe active"--> LP
    LP --> LP_Check
    LP_Check --"Success"--> LP_Pass
    LP_Check --"3 failures"--> LP_Fail
    LP_Pass --> Running
    LP_Fail --> Failed
    
    Ready --"Readiness probe active"--> RP
    RP --> RP_Check
    RP_Check --"Success"--> RP_Pass
    RP_Check --"3 failures"--> RP_Fail
    RP_Pass --> Running
    RP_Fail --"Remove from LB<br/>(container stays alive)"--> Running
    
    Failed --"Restart with<br/>exponential backoff"--> Start

Kubernetes implements three distinct probe types that operate at different lifecycle stages. Startup probes handle slow initialization with long timeouts (300s total in this example), preventing premature liveness check failures. Once startup succeeds, liveness probes detect dead


Interview Expectations

Mid-Level

What You Should Know

At the mid-level, you should be able to explain the basic concept of health endpoints and implement a simple health check for a web application. You need to understand the difference between liveness and readiness checks, and why both are important. You should be able to describe what checks a typical health endpoint would include (database connectivity, cache availability) and explain why health checks need timeouts. You should understand how load balancers use health checks to route traffic and how this prevents routing requests to failed instances.

You should be able to design a basic health endpoint that checks database connectivity with a simple query like SELECT 1, verifies cache availability, and returns appropriate HTTP status codes (200 for healthy, 503 for unhealthy). You should know that health checks should be fast (under 1 second) and explain why slow health checks are problematic.

Bonus Points

Bonus points for discussing the importance of health check timeouts and what happens when they’re missing (health checks can hang, causing monitoring systems to timeout). Extra credit for mentioning that health checks shouldn’t create new work (like opening new database connections) but should observe existing state (like checking if connection pools have available connections). You’ll impress interviewers if you mention that health checks need to be monitored themselves—if health checks start failing across all instances simultaneously, it might indicate a monitoring system problem rather than application problems.

Senior

What You Should Know

At the senior level, you should be able to design comprehensive health monitoring strategies for distributed systems. You need to understand the tradeoffs between different health check approaches (shallow vs. deep, aggressive vs. conservative polling) and make informed decisions based on system requirements. You should be able to explain how health checks integrate with multiple infrastructure layers (load balancers, container orchestrators, monitoring systems) and design checks that work well across all these layers.

You should be able to design health checks that support graceful degradation—categorizing dependencies as critical vs. optional and returning appropriate status when optional dependencies fail. You need to understand the risks of poorly designed health checks (creating new failure modes, overwhelming dependencies, causing cascading failures) and how to avoid them. You should be able to calculate health check overhead and determine appropriate check intervals based on SLA requirements and system scale.

You should discuss how health checks fit into broader reliability strategies like circuit breakers, bulkheads, and retry policies. You need to understand how to prevent health check false positives (using failure thresholds, implementing check result caching) and false negatives (ensuring checks actually verify critical functionality).

Bonus Points

Bonus points for discussing advanced patterns like client-side health checking (where calling services maintain their own health scores for dependencies), graduated health scores (instances reporting 0-100% health rather than binary healthy/unhealthy), and health check result aggregation across multiple monitoring locations. Extra credit for explaining how to handle health checks during deployments (using startup probes, implementing health check delays for warming up) and how to design health checks that work in multi-region deployments (checking regional dependencies separately from global dependencies). You’ll stand out if you can discuss specific incidents where health check design prevented or caused outages, showing you’ve learned from real-world experience.

Staff+

What You Should Know

At the staff+ level, you should be able to architect organization-wide health monitoring strategies that balance observability with system stability. You need to understand how health check design decisions affect system behavior at scale—how check intervals and thresholds impact failure detection time, how health check traffic affects infrastructure costs, and how health check design influences incident response procedures. You should be able to design health monitoring that works across heterogeneous systems (microservices, batch jobs, data pipelines) with different availability requirements.

You should be able to make sophisticated tradeoff decisions about health check design based on business requirements. For example, deciding whether to use fail-fast or graceful degradation based on revenue impact of partial functionality versus full outages, or determining appropriate check intervals based on SLA requirements and cost constraints. You need to understand how health checks interact with other reliability mechanisms and design systems where these mechanisms work together rather than interfering.

You should be able to identify and prevent subtle failure modes in health check design. For example, understanding how health checks can create thundering herds during recovery (all instances becoming healthy simultaneously and overwhelming dependencies), how health check false positives can cause availability loss during network partitions, and how health check design affects incident response (whether operators can trust health check results or need to verify independently).

Distinguishing Signals

Staff+ engineers distinguish themselves by discussing health monitoring as part of a comprehensive reliability strategy rather than an isolated pattern. You should be able to explain how health check design affects organizational practices—for example, how detailed health check responses enable better runbooks and faster incident resolution, or how health check metrics inform capacity planning decisions. You should discuss the evolution of health monitoring approaches as systems scale, explaining why strategies that work at 10 instances fail at 1000 instances.

You’ll stand out by discussing the economics of health monitoring—calculating the cost of health check traffic and infrastructure, comparing this to the cost of undetected failures, and making data-driven decisions about monitoring investment. You should be able to explain how health check design affects different stakeholders (developers, operators, SREs) and design systems that serve all stakeholders’ needs. For example, developers need detailed diagnostic information for debugging, operators need reliable signals for incident response, and SREs need metrics for capacity planning.

Top candidates discuss specific architectural decisions they’ve made around health monitoring and their long-term consequences. For example, explaining why they chose to implement client-side health checking in addition to centralized monitoring, or why they designed health checks to support graduated health scores rather than binary status. You should be able to critique common health monitoring patterns and explain when they’re appropriate versus when they cause problems.

Common Interview Questions

Question 1: How would you design a health endpoint for a web application that depends on a database, cache, and downstream API?

60-second answer: I’d expose two endpoints: /health/live for liveness (just returns 200 OK) and /health/ready for readiness. The readiness endpoint would check database connectivity with a simple SELECT 1 query (100ms timeout), verify cache availability with a GET operation (100ms timeout), and check the downstream API by examining the circuit breaker state rather than making an actual call. Each check runs in parallel with timeouts to prevent slow dependencies from blocking. The endpoint returns 200 if all critical checks pass, 503 if any fail, with JSON details about which checks failed. Total response time should be under 500ms.

2-minute detailed answer: I’d start by categorizing dependencies as critical versus optional. The database and cache are critical—we can’t serve requests without them. The downstream API might be optional if we can serve cached responses. For the liveness endpoint /health/live, I’d return 200 OK immediately without any dependency checks—this is purely to detect if the process is alive. For the readiness endpoint /health/ready, I’d implement parallel checks with strict timeouts. The database check would use a connection from the existing pool and execute SELECT 1 with a 100ms timeout—this verifies both connectivity and that the pool isn’t exhausted. The cache check would attempt a GET operation on a known key with a 100ms timeout. For the downstream API, I’d check the circuit breaker state rather than making an actual call—if the circuit breaker is open, we know the API is unhealthy without adding more load. I’d aggregate results and return 200 if all critical checks pass, 503 if any critical check fails, with a JSON payload detailing each check’s status. I’d also include metrics like check duration and last success timestamp. The implementation would cache results for 5-10 seconds to prevent health check traffic from overwhelming dependencies during incidents. Finally, I’d expose a /health/debug endpoint with more detailed diagnostics that’s only accessible from internal networks, preventing information disclosure while enabling debugging.

Red flags: Saying you’d make actual HTTP calls to downstream services in health checks (creates additional load and can cause cascading failures). Not mentioning timeouts (health checks can hang). Suggesting health checks should test complex business logic (too slow and creates new failure modes). Not distinguishing between liveness and readiness (shows lack of understanding of how orchestrators use health checks).

Question 2: Your health checks are causing false positives—instances are being marked unhealthy when they’re actually fine. How would you debug and fix this?

60-second answer: First, I’d check health check logs to see which specific checks are failing and whether failures are consistent or intermittent. If failures are intermittent, it suggests timeout issues or transient network problems. I’d increase failure thresholds (require 3-5 consecutive failures instead of 1) to reduce sensitivity. I’d verify that health check timeouts are appropriate—if checks are timing out, I’d either increase timeouts or optimize the checks. I’d also check if health checks are creating new work (like opening new connections) rather than observing existing state, which can cause resource exhaustion.

2-minute detailed answer: I’d start by examining health check metrics to understand the failure pattern. Are failures happening across all instances simultaneously (suggests monitoring system issues or shared dependency problems) or on individual instances (suggests instance-specific issues)? Are failures intermittent or sustained? I’d look at health check response times—if they’re approaching timeout values, the checks might be too slow. Next, I’d review the health check implementation to identify potential issues. Common problems include: checks that open new database connections instead of using the connection pool (can exhaust connections), checks that make synchronous calls to slow dependencies (should use circuit breaker state instead), checks without proper timeouts (can hang), and checks that are too sensitive to transient issues. I’d implement check result caching (5-10 seconds) to prevent health check traffic from overwhelming dependencies during incidents. I’d increase failure thresholds to require 3-5 consecutive failures before marking unhealthy—this reduces false positives from transient network issues. I’d also implement graduated health checks where minor issues don’t immediately mark instances unhealthy but instead report degraded status. Finally, I’d add detailed logging to health checks to capture why checks are failing—this diagnostic information is crucial for debugging false positives. If the problem persists, I’d consider implementing passive health checks (monitoring actual traffic errors) in addition to active checks, providing a second signal that can validate active check results.

Red flags: Immediately suggesting to disable health checks (shows lack of commitment to reliability). Not considering that the health checks themselves might be the problem (shows lack of systems thinking). Suggesting to just increase timeouts without investigating root cause (treats symptoms rather than disease). Not mentioning the importance of failure thresholds and check result caching.

Question 3: How would you design health checks for a system that needs to handle graceful degradation—continuing to serve some requests even when dependencies are failing?

60-second answer: I’d categorize dependencies as critical, important, and optional. Critical dependencies (like the database) must be healthy for the service to function at all. Important dependencies (like the cache) enable full functionality but the service can operate without them using fallbacks. Optional dependencies (like analytics) don’t affect core functionality. The health endpoint would return different status codes: 200 for fully healthy, 200 with a “degraded” indicator when important dependencies fail, and 503 only when critical dependencies fail. This lets load balancers keep degraded instances in rotation while preferring fully healthy instances.

2-minute detailed answer: I’d start by working with product teams to categorize each dependency based on business impact. For an e-commerce site, the product database is critical (can’t show products without it), the recommendation engine is important (site works but user experience degrades), and the analytics system is optional (doesn’t affect user-facing functionality). I’d design the health endpoint to return a structured response indicating overall status plus per-dependency status: {"status": "degraded", "critical": {"database": "healthy"}, "important": {"cache": "unhealthy", "recommendations": "healthy"}, "optional": {"analytics": "healthy"}}. The HTTP status code would be 200 for both healthy and degraded states (so load balancers keep the instance in rotation) but the JSON payload would indicate degradation. I’d configure load balancers to prefer fully healthy instances but route to degraded instances when necessary. I’d also implement feature flags that automatically disable non-critical features when their dependencies are unhealthy—for example, disabling personalized recommendations when the recommendation engine is down rather than showing errors. The application would need circuit breakers for each dependency so it can quickly detect failures and switch to degraded mode without waiting for timeouts on every request. I’d expose separate health endpoints for different dependency categories (/health/critical, /health/important) so monitoring systems can alert differently based on which dependencies are failing—critical dependency failures page on-call engineers immediately, while important dependency failures create tickets for investigation during business hours. This approach maximizes availability by keeping partially functional instances in service while still providing visibility into what’s degraded.

Red flags: Treating all dependencies as equally critical (shows lack of product thinking). Not considering how load balancers interpret different health statuses (shows lack of infrastructure knowledge). Suggesting the application should continue attempting to use failed dependencies (shows lack of understanding of circuit breakers). Not discussing how to communicate degraded status to monitoring systems and operators.

Question 4: At what interval should you run health checks, and how do you determine the right frequency?

60-second answer: The interval depends on your failure detection SLA and system scale. If you need to detect failures within 30 seconds, and you require 3 consecutive failures before marking unhealthy, you need to check at least every 10 seconds (10 seconds × 3 failures = 30 seconds). But you also need to consider overhead—1000 instances checked every 10 seconds from 3 monitoring locations means 300 requests/second. For most systems, 10-30 seconds for readiness checks and 10-15 seconds for liveness checks provides good balance between detection speed and overhead.

2-minute detailed answer: The right interval is determined by three factors: failure detection SLA, system scale, and health check reliability. Start with your SLA—if you promise 99.9% availability, you can afford 43 minutes of downtime per month. If each failure takes 30 seconds to detect and route around, and you have 20 failures per month, that’s 10 minutes consumed just by detection delay. To reduce this, you’d need faster health checks. Calculate the maximum detection time as: (check interval × failure threshold) + check timeout. If you need to detect within 30 seconds, require 3 consecutive failures, and have 2-second timeouts, you need: (interval × 3) + 2 ≤ 30, so interval ≤ 9.3 seconds. Next, calculate overhead: (number of instances × monitoring locations) / interval = requests per second. For 1000 instances with 3 monitoring locations checking every 10 seconds: 300 requests/second. If each check consumes 50ms of CPU, that’s 15 CPU cores just for health checks. You need to ensure this overhead is acceptable. Finally, consider health check reliability—if checks are flaky and produce false positives, you need longer failure thresholds (more consecutive failures required), which means you need shorter intervals to maintain the same detection time. In practice, most systems use 10-30 seconds for readiness checks (which can be more thorough) and 10-15 seconds for liveness checks (which should be very lightweight). User-facing services typically use shorter intervals (10-15 seconds) while background services use longer intervals (30-60 seconds). You should also implement exponential backoff for unhealthy instances—once an instance is marked unhealthy, you can check it less frequently (every 60 seconds) until it recovers, reducing overhead on failed instances.

Red flags: Suggesting very short intervals (< 5 seconds) without considering overhead (shows lack of scale awareness). Not mentioning failure thresholds when calculating detection time (incomplete understanding). Suggesting the same interval for all services regardless of criticality (shows lack of nuance). Not considering the relationship between check interval and check timeout.

Question 5: How do health checks interact with circuit breakers, and why would you use both?

60-second answer: Health checks and circuit breakers serve different purposes and operate at different layers. Health checks are external monitoring—a monitoring system checks if your service is healthy. Circuit breakers are internal protection—your service monitors its dependencies and stops calling them when they’re failing. You need both because health checks detect when your service is unhealthy (so load balancers can route around it), while circuit breakers prevent your service from making things worse by continuing to call failed dependencies. They work together: when a circuit breaker opens (dependency is failing), your health check can report this as degraded status.

2-minute detailed answer: Health checks and circuit breakers are complementary patterns that protect different parts of your system. Health checks are external monitoring—they let infrastructure (load balancers, orchestrators) know whether your service should receive traffic. Circuit breakers are internal protection—they prevent your service from repeatedly calling failed dependencies, giving those dependencies time to recover. You need both because they solve different problems. Without health checks, your service might continue receiving traffic even when it can’t fulfill requests (because its dependencies are down). Without circuit breakers, your service would continue hammering failed dependencies, making recovery harder and wasting resources on calls that will fail. The patterns integrate well: your health check implementation should examine circuit breaker states rather than making actual calls to dependencies. If the circuit breaker to your database is open, your health check knows the database is unhealthy without making another failing call. This prevents health checks from adding load to already-struggling dependencies. The health check can then report degraded status (if the dependency is optional) or unhealthy status (if the dependency is critical). Circuit breakers also help prevent health check false positives—instead of timing out on every health check call to a slow dependency, the circuit breaker opens after a few failures, and subsequent health checks immediately know the dependency is unhealthy by checking the circuit breaker state. In a well-designed system, you’d have circuit breakers for each dependency, health checks that examine circuit breaker states, and monitoring that tracks both health check results and circuit breaker state transitions. During an incident, you’d see circuit breakers opening (indicating dependency failures), followed by health checks reporting degraded/unhealthy status (indicating the service can’t fulfill requests), followed by load balancers routing traffic away (protecting users from errors). This layered defense provides both fast failure detection and protection against cascading failures.

Red flags: Saying health checks and circuit breakers are redundant (shows lack of understanding of their different purposes). Suggesting health checks should call dependencies directly without checking circuit breaker state (creates additional load on failed dependencies). Not understanding that circuit breakers are internal (within your service) while health checks are external (monitoring your service). Confusing circuit breakers with retries or timeouts.