DevOps Observability Interview Questions: Logs, Metrics, and Traces

Reviewed by Mark Dickie · Last updated

Observability in DevOps is the practice of instrumenting systems so you can understand their internal state from external outputs, primarily logs, metrics, and traces. An interview on this topic checks whether you can distinguish those three signal types, explain when each one answers a specific debugging question, and reason about trade-offs like cardinality and retention cost. You should also be able to describe how the three connect during an incident: a metric fires an alert, a log pinpoints the error, and a trace walks the request path across service boundaries.

Signal typeQuestion it answersCommon toolsCost driver
LogsWhat happened?ELK, Loki, FluentdIngestion volume and retention
MetricsHow much, how often?Prometheus, Datadog, InfluxDBLabel cardinality
TracesWhere did it go, how long?Jaeger, Zipkin, OpenTelemetrySampling rate and span storage

What are the three pillars of observability?

  1. Logs are discrete, timestamped records of events. Each line tells you what happened at a point in time but carries no built-in aggregation.
  2. Metrics are numeric measurements collected at intervals. Cheap to store and query across time ranges, but per-event detail is lost in aggregation.
  3. Traces are trees of spans following a single request across service boundaries. They show causality and per-hop timing that logs and metrics cannot reveal alone.

How do logs, metrics, and traces fit together during an incident?

A metric alert tells you something broke — say, the error rate on the checkout service spiked. You open a dashboard to narrow down which endpoint and which time window. Then you search logs for that service around the spike to find the actual error message or stack trace. A distributed trace lets you follow one failing request end-to-end to see which downstream call timed out. Each pillar fills a gap the other two leave open, and interviewers often test whether you can move between them in the right order.

What trade-offs should I know for a logs, metrics, and traces interview?

Cardinality is the most common trap. Every unique label combination on a metric creates a new time series, so a label like user_id or request_id can explode your storage and query cost overnight. Logs are the opposite problem: they are cheap to produce but expensive to retain, so most teams apply retention tiers and structured logging to keep them searchable. Traces sit in between — you sample because storing every span at full fidelity is rarely affordable, and head-based sampling captures the same percentage of every request while tail-based sampling keeps the interesting ones (errors, slow requests).

Key facts

  • Tarmac's DevOps interview questions cover 17 questions at difficulty 1–5 of 5.
  • Tarmac tracked 2,674 job postings asking for DevOps in August 2026.
  • Roles asking for DevOps advertise a median base salary of £80,000, across 596 job postings as of August 2026.
  • Tarmac last reviewed these DevOps interview questions on 31 August 2026.

At a glance

Questions17
Difficulty1–5 of 5
FormatsMultiple choice, Code output, True / false, Multiple answer, Short answer, Flashcard, Ordering, Find the bug

What you'll review

  1. logs metrics traces
  2. structured logging

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

A user's request touches five microservices before it fails deep in the chain. What is the standard way to find every log line generated by that one request, across all five services?#

Options

Show answer

A correlation ID is a unique identifier generated at the point a request first enters the system and passed to every downstream service call, usually via a request header. Every service logs that same ID alongside its own entries, so a single search on the ID pulls every log line the request touched, across every service, into one causal chain. Distributed-tracing trace IDs serve the same correlating role. Without a shared ID, per-service request IDs differ at each hop and timestamps alone can't disambiguate concurrent traffic.

Why:

A correlation ID is a unique identifier minted at the edge of a request (an API gateway or the first service it hits) and threaded through every downstream call, usually as a header (e.g. X-Correlation-ID, or the trace ID a distributed-tracing system generates, which serves the same purpose). Every service that touches the request logs that ID alongside its own log lines, so a single search or filter on the ID reconstructs the request's full path through the system regardless of how many services it crossed. Searching each service's logs by timestamp alone is unreliable — under real concurrent traffic, many unrelated requests log in the same millisecond, and clock skew across hosts makes timestamp correlation worse, not better. Relying on each service's own locally-generated request ID gets the mechanism backwards: a request ID generated independently inside each service is, by definition, a different value per hop unless it is explicitly passed in and reused — that's exactly the bug the correlation-ID pattern exists to prevent. The claim that there is no standard mechanism for this is false; this is a solved, standard practice, and its absence is a common root cause of 'we can't tell what happened' during an incident.

A service is up but slow, and you cannot tell which downstream call is responsible. Which of the 'three pillars' of observability is designed to answer 'where in the request path is the time going?'#

Options

Show answer

Distributed tracing answers this. A trace stitches together the spans of a single request as it crosses service boundaries, with each span timed, so you can read off exactly which downstream hop dominates the latency. Metrics aggregate away the per-request path — they tell you that latency is up, not where. Logs are per-service discrete events that don't automatically reconstruct one request's journey without a shared trace or correlation id. The three pillars are logs, metrics, and traces; alerting is built on them, not a separate pillar.

Why:

The three pillars are logs, metrics, and traces, and each answers a different question. Metrics are cheap aggregate time-series — great for 'is latency up?' and dashboards/alerts, but they aggregate away the per-request path, so they tell you that something is slow, not where in a multi-service call. Logs are discrete events rich in detail, but on their own they are per-service and don't automatically reconstruct one request's journey across services. Distributed tracing is purpose-built for this: a trace ties together the spans of a single request as it crosses service boundaries, each span timed, so you can read off exactly which downstream hop dominates the latency. The claim that a single latency gauge tells you exactly which service is slow overstates what a single metric can do; the claim that grepping each service's log lines reconstructs the full causal request path automatically assumes logs auto-correlate across services (they don't without a shared trace/correlation id); the claim that alerting is the fourth pillar and pinpoints the slow hop directly is wrong — alerting is built on metrics/logs, it is not a fourth pillar and does not localize a slow span.

Two services log the same checkout event. Service A emits the first line, Service B the second. A log platform ingests both and you need to alert when amount > 100 for a given user_id. Which line lets you build that query reliably without brittle text parsing?#

A: User 4823 checked out for $142.50 successfully
B: {"event":"checkout","user_id":4823,"amount":142.50,"status":"ok"}

Options

Show answer
Line B — it is structured (JSON) with typed, named fields, so the platform indexes user_id and amount and you can query amount > 100 directly
Why:

Line B is structured logging: a machine-readable object with named, typed fields. A log platform parses it into indexed fields (user_id as a number, amount as a number), so a query like event:checkout AND amount > 100 is exact and survives wording changes. Line A is a human sentence — to extract the amount you'd need a fragile regex that breaks the moment someone changes 'checked out for $' to 'purchased', drops the dollar sign, or localizes the message, and the value arrives as text, not a comparable number. That brittleness is exactly why production systems standardize on structured logs with consistent field names (and a correlation/trace id) across services. The claim that free-text logs are easier for machines to query has it backwards; prose is easy for humans, hard for machines. The claim that structure makes no difference is wrong because structure is precisely what makes reliable field queries possible. The claim that numeric thresholds can never be evaluated from logs is false — you absolutely can threshold over a parsed numeric log field; metrics are often derived from such logs, but the log field itself is queryable.

In SRE terminology, what is the precise difference between an SLI and an SLO?#

Options

Show answer

An SLI (service level indicator) is the actual measured quantity, defined from the user's perspective — request success rate, a latency percentile, throughput, or freshness are all valid SLIs. An SLO (service level objective) is the target set on top of that indicator, such as '99.9% of requests succeed over 28 days,' turning a raw measurement into an accountability contract. Neither term is tied to one fixed dimension: an SLI is not always availability, and an SLO is not always latency.

Why:

The Google SRE book draws this distinction precisely: an SLI is a quantitative measure of some aspect of service behavior, defined from the user's perspective — request success rate, a latency percentile, throughput, freshness, or durability are all valid SLIs. An SLO is a target value or range set on top of an SLI over a defined compliance window (e.g. 99.9% success over 28 days), and it is what turns a raw measurement into an accountability contract between the teams building and operating the service. The claim that an SLI is a target set by the business and an SLO is whatever the monitoring system happens to measure inverts the relationship — the SLI is the measurement, not a business-set target, and the SLO is the deliberately chosen target, not an incidental readout. The claim that SLI and SLO are interchangeable terms for the same threshold collapses a meaningful distinction; they are different objects (measurement vs. target on that measurement), not synonyms. The claim that an SLI is always an availability percentage and an SLO is always a latency number is a common misconception: neither term is tied to a fixed dimension — an SLI can just as easily be a latency percentile as an availability ratio, and an SLO can be set on any of them.

An SLI is restricted to measuring binary availability — a request either succeeded or failed — and cannot express a continuous quantity like a latency percentile or request throughput.#

Options

Show answer

SLIs are not restricted to availability. The Google SRE book defines an SLI as any carefully defined quantitative measure of service behavior, and the canonical list explicitly includes latency percentiles, throughput, error rate, durability, and correctness alongside availability. A '95th-percentile latency under 300ms' SLI or a 'requests per second' throughput SLI are both completely standard. Teams pick whichever SLI best reflects what users experience for that particular service, rather than defaulting to a single up/down measure.

Why:

False. Per the Google SRE book, an SLI is any carefully defined quantitative measure of some aspect of the level of service, and the canonical examples explicitly include request latency (commonly a percentile like p95 or p99), throughput, error rate, system durability, and correctness — availability is only one of several common choices, not a definitional limit. A latency SLI ('95th percentile response time under 300ms') and a throughput SLI ('requests served per second above X') are both completely standard. Teams choose the SLI that best represents what users actually experience for a given service — a batch pipeline might care about freshness or correctness, an interactive API about latency, a payments service about both availability and correctness — so treating 'up vs. down' as the only valid shape for an SLI would rule out most of the SLIs real SRE teams actually track.

You need to alert immediately whenever the checkout service's error rate crosses 1%, and keep a full year of that error-rate trend on a dashboard, at minimal storage cost. Which observability signal should be the primary tool, and why?#

Options

Show answer

Metrics are the right tool here. A metric is a pre-aggregated numeric time series bucketed at fixed intervals, so its storage grows with bucket count, not raw request volume, and evaluating a threshold against a rolled-up counter stays cheap even a year later — exactly what alerting and long-retention dashboards need. Logs are expensive to retain in full and re-scan on every evaluation. Traces are sampled and optimized for explaining individual requests, so a sampled set gives an estimate, not an exact aggregate rate.

Why:

Metrics are pre-aggregated numeric time series bucketed at fixed intervals, so their storage grows with the number of buckets over time, not with raw request volume, and evaluating a threshold against a rolled-up counter is cheap even a year later — which is precisely why alerting rules and long-retention trend dashboards are built on metrics. The description of logs is the anti-pattern: logs are high-volume, often high-cardinality, and expensive to retain in full and re-scan on every evaluation; using raw logs as the alerting primary doesn't scale and is why metrics exist as a separate, cheaper signal. Traces are wrong for two reasons — traces are typically sampled (often well under 100%) specifically because storing every one is too costly, so a sampled set gives an estimate, not the exact rate, and traces are optimized for explaining one request's latency/causality, not aggregating a year of trend data. The claim that none of the three work is false: metrics, via Prometheus, a hosted time-series database, or an APM platform's own metric pipeline, are the standard mechanism for exactly this use case.

Which statements about OpenTelemetry are correct? Select all that apply.#

Options

Pick every one that applies.

Show answer

OpenTelemetry is a vendor-neutral instrumentation layer covering traces, metrics, and logs through one API/SDK, so code is instrumented once regardless of destination. Context propagation — serializing an active span's trace ID and span ID into outgoing headers, such as the W3C Trace Context 'traceparent' header, and reading it back downstream — is what lets independently-generated spans from separate services assemble into a single trace. Because instrumentation is decoupled from the backend, switching observability vendors is normally a config change, not a rewrite. OpenTelemetry does not itself store or visualize data; that still requires a backend.

Why:

OpenTelemetry (a CNCF project) provides one vendor-neutral API/SDK surface across traces, metrics, and logs, so instrumentation is written once against the OpenTelemetry API rather than against a specific vendor's proprietary client. The mechanism that turns independently-created spans on separate services into a single coherent trace is context propagation: the active span's context is serialized into a standard header on outgoing calls and extracted on the receiving side to seed a child span with the correct parent — this is exactly what the W3C Trace Context standard formalizes, and it's the core concept enabling distributed tracing at all. Because the instrumentation API is decoupled from the destination, switching backends is normally an exporter/collector configuration change, not an application rewrite — this is the practical payoff of vendor neutrality and the main reason teams adopt it. The claim that OpenTelemetry itself replaces the need for an observability backend is false: OpenTelemetry is the instrumentation, collection, and export layer; it deliberately does not include a storage/visualization backend, so you still need something like Prometheus, Jaeger, Tempo, or a commercial APM to store and view the data. The claim that OpenTelemetry only covers distributed tracing is false: traces, metrics, and logs are all first-class OpenTelemetry signals, not an out-of-scope extra.

An on-call rotation is burning out: dozens of pages fire every night, most are closed with no action taken, and the team has started muting the pager channel. Which statements about this alert-fatigue problem and its fix are correct? Select all that apply.#

Options

Pick every one that applies.

Show answer

Alert fatigue is the desensitization that sets in once alert volume and false-positive rate get high enough that responders start dismissing pages, including real ones. The fix is to alert on user-facing symptoms — elevated error rate, an SLO's error budget burning too fast — rather than every internal cause metric like CPU or disk usage, since a cause alone doesn't confirm user impact. Every alert should also pass the test of naming a concrete human action; if the answer is nothing, it shouldn't page anyone. Lowering thresholds or muting a noisy channel both make the underlying problem worse, not better.

Why:

Alert fatigue is precisely the desensitization pattern described: once responders are paged too often for things that turn out not to matter, they start treating every page — including the ones that do matter — as noise to dismiss, which is how real incidents get missed. The two standard fixes: alert on symptoms tied to what users actually experience (error rate, latency, SLO burn rate) rather than on every internal resource metric that merely correlates with a problem — a service can run hot on CPU with zero user impact, or fail users while every resource metric looks nominal, so causes alone are poor alerting signal; and hold every alert to the bar of 'what does a human do when this fires?' — if nothing, or if the response is fully automatable, it shouldn't be paging anyone. The claim that the fix is to lower every alert's threshold so issues are caught earlier is the opposite of a fix: lowering thresholds increases alert volume and false-positive rate, which is the direct driver of fatigue, not a cure for it. The claim that muting the noisy alert channel is an acceptable long-term fix treats a symptom-suppressing workaround as a solution — muting a noisy channel just hides the signal-to-noise problem instead of fixing it, and it reintroduces the exact risk of missing a real incident that alert fatigue already causes.

What problem is OpenTelemetry designed to solve for teams instrumenting their applications, and why does its vendor-neutrality matter in practice?#

Show answer

Before OpenTelemetry, each observability vendor shipped its own proprietary instrumentation SDK, so adopting one meant hand-instrumenting the codebase against that vendor's specific API, and switching vendors later meant ripping out and redoing that instrumentation across the whole codebase — a strong lock-in force. OpenTelemetry is a CNCF project providing one vendor-neutral set of APIs, SDKs, and data formats covering all three observability signals — traces, metrics, and logs — so a team instruments its code once against the OpenTelemetry API rather than against any particular backend's client library. Where that telemetry data actually goes is a separate, swappable concern, configured through an exporter (and often routed through the optional OpenTelemetry Collector, which can receive, batch, sample, and forward data from many services before it reaches a backend). In practice this means a team can point the same instrumented application at an open-source backend like Jaeger or Prometheus today, and later move to a commercial APM vendor by reconfiguring the exporter and collector pipeline — not by re-instrumenting the application. Vendor-neutrality matters because it decouples the (expensive, code-touching) instrumentation decision from the (comparatively cheap, config-only) backend decision.

Why:

The interview signal here is whether the candidate understands OpenTelemetry as an instrumentation-layer standard, not a product. Before it existed, every vendor's proprietary SDK created switching costs proportional to how much code touched it, since instrumentation is spread throughout an application. OpenTelemetry's contribution is standardizing the API/SDK surface across all three signals (traces, metrics, logs) so instrumentation code becomes vendor-agnostic, and pushing the vendor-specific decision down to a swappable exporter/collector configuration. A strong answer names the lock-in problem being solved, states that OTel covers traces + metrics + logs (not tracing alone), and explains the practical consequence: changing backends becomes a configuration change rather than a re-instrumentation project. Candidates who only say 'it's for tracing' or 'it collects data' are missing the vendor-neutrality point the question is actually probing.

How do an SLI, an SLO, and an error budget relate to each other?#

Show answer

An SLI (service level indicator) is the actual quantitative measurement of some aspect of service behavior from the user's perspective — for example, 'percentage of requests that returned a non-5xx status over the last 28 days,' or a latency percentile. An SLO (service level objective) is a target set on top of that SLI — for example, '99.9% of requests succeed.' The error budget is what's left over: 100% minus the SLO, so a 99.9% SLO leaves a 0.1% error budget over the measurement window. Concretely, if the service handles 1,000,000 requests over 28 days, a 99.9% SLO permits roughly 1,000 failed requests before the budget is exhausted. The three form a stack, in order: you measure with the SLI, you set a target on that measurement with the SLO, and the error budget is the operational slack derived from that target — spent on deploy risk and experimentation while it remains, and its exhaustion is what triggers a reliability-first policy (freeze risky releases, redirect effort to fixes) until the service earns budget back.

Why:

This is foundational SRE vocabulary, and interviewers listen for the stack order rather than just the definitions in isolation: measure (SLI) → set a target on the measurement (SLO) → derive operational slack from the target (error budget). A strong answer also grounds it with a concrete numeric example (a 99.9% SLO on 1,000,000 requests over 28 days permits ~1,000 failures) and names the operational consequence of the budget — it isn't just an accounting artifact, it's the number that decides whether the team ships fast or slows down to firefight. Candidates who define all three correctly but can't say how spending/exhausting the budget changes team behavior are missing the practical half of the concept.

Order the steps of how a single distributed trace comes together, from a request first entering your system to an engineer viewing the assembled trace.#

Put these in order

Show answer

A distributed trace forms in five steps. First, a request with no existing trace context triggers the entry service to mint a new trace ID and open a root span. Before calling downstream, that span's context is injected into the outgoing request's headers (the W3C 'traceparent' header). The downstream service extracts that context and opens a child span recording its caller as parent — repeating at every hop. Each service exports its finished spans independently to a collector. Only then does the backend group every span sharing the trace ID and assemble them by parent/child order into one viewable trace.

Why:

This is the context-propagation mechanism that OpenTelemetry describes as the core concept enabling distributed tracing at all. It starts when a request arrives with no existing trace context, so the first service acts as the entry point: it mints a new trace ID and opens the root span. Before that service calls anything downstream, it serializes its current span's identifiers into the outgoing request — standardized today as the W3C Trace Context 'traceparent' header — so the propagation format is consistent across languages and vendors. The receiving service extracts that header, and instead of starting an unrelated new trace, it opens a child span that records the caller's span ID as its parent, which is what stitches the two services' work into one causal chain (this inject/extract pair repeats at every hop in a multi-service call). Each service exports its own finished spans, independently and typically batched, to a collector or backend — export doesn't wait for the whole request to finish. Only at the backend does assembly happen: every span sharing the trace ID gets grouped and ordered by its parent/child links into the waterfall view engineers actually read to see which hop dominated the latency. Getting inject/extract in the wrong order relative to the call, or forgetting that assembly is a backend-side step rather than something each service does locally, are the common misconceptions this ordering surfaces.

A team adds a user_id label to a Prometheus counter that tracks API requests, so they can filter per-user in Grafana. Within a day the Prometheus server starts running out of memory and queries time out. What's happening, and what's the fix?#

Options

Show answer

Cardinality explosion is what's happening. Prometheus stores every unique combination of a metric's label values as its own time series in memory, and a label like user_id can take millions of values, so it multiplies the series count by that unbounded factor until the server runs out of memory. Bounded labels like status_code or method are safe because they take only a handful of values. The fix is to drop unbounded-value labels from metrics and use logs or traces — which are indexed for high-cardinality point lookups — to find one user's activity.

Why:

A Prometheus time series is identified by its metric name plus its full set of label key/value pairs; every distinct combination that has ever been observed becomes its own series, held in memory (and on disk) for as long as it's retained. Bounded labels like method or status_code take a handful of values, so they multiply the series count by a small, known factor. user_id is effectively unbounded — one new value per user, potentially millions — so attaching it to a metric multiplies the series count by that unbounded factor, which is exactly the 'cardinality explosion' that exhausts memory and slows every query that has to scan more series. The fix is architectural: keep metric labels to bounded, low-cardinality dimensions, and route anything you need to look up per-entity (a specific user, request, or session) to logs or traces, which are indexed for point lookups over high-cardinality fields. The fixed-size label table claim misdescribes the mechanism — memory cost scales with distinct label values, not with label count alone. The claim that it is self-resolving is false; Prometheus doesn't quietly evict live series to relieve memory pressure the way that claim implies — teams that hit the ceiling end up dropping metrics or shortening retention themselves, losing exactly the data they need. Blaming the Counter instead of the label is a distractor: Counter vs. Gauge is unrelated to cardinality.

A team wants to keep every trace that contains an error, while sampling normal successful traces at just 1%. Which sampling strategy achieves this, and why can't simple head-based sampling do it?#

Options

Show answer

Tail-based sampling achieves this. It buffers all of a trace's spans until the trace completes, then decides whether to keep it based on the whole outcome — for example, always keeping traces with an error and sampling successful ones at 1%. Head-based sampling decides per trace ID up front, typically at the root span, before the eventual outcome is known, so it cannot condition its decision on whether an error will occur. The trade-off for tail-based sampling is buffering infrastructure and added export latency.

Why:

Head-based sampling makes its keep/drop decision early — typically a consistent hash of the trace ID compared against a target rate, decided at or near the root span, before most of the downstream calls have even happened. Because that decision is made before the request has run its course, it cannot be conditioned on an outcome (like an eventual error) that hasn't happened yet. Tail-based sampling defers the decision until the full trace has been collected — its spans buffered, typically at a collector — which lets it apply outcome-aware policies such as 'always retain traces that contain an error, sample the rest at 1%.' The cost is real: tail-based sampling requires buffering infrastructure and adds latency before spans can be exported, since the collector must wait to see the whole trace. The claim that head-based sampling can conditionally keep only error traces is wrong by definition — head-based sampling can't conditionally keep only error traces, because the decision point predates knowledge of the outcome. The claim that the two approaches are the same algorithm under different names is false; the two approaches differ specifically in when the decision is made, which is the whole reason they support different guarantees. The claim that neither approach can retain 100% of error traces while sampling successes at a lower rate is false — combining a floor rate for successes with guaranteed retention for errors is precisely the standard tail-based sampling use case.

If your application uses an OpenTelemetry tracing SDK, a bare console.log/print statement inside a traced function will automatically include that span's trace ID and span ID, with no logging-specific configuration, simply because the trace and the log statement run in the same process.#

Options

Show answer

False. A plain console.log/print call bypasses OpenTelemetry's logging pipeline entirely, so it will not include the active span's trace_id or span_id just because tracing and logging happen to run in the same process. Getting trace_id/span_id into log records requires OpenTelemetry's logging signal or bridge to read the active span context and inject those fields — sometimes a one-line configuration step, sometimes automatic once that integration is wired up, but never a byproduct of shared process memory alone.

Why:

False — correlating a log line with the active trace requires the OpenTelemetry logging signal or bridge to read the active span's context and inject trace_id/span_id into the emitted record; it doesn't happen just because tracing and logging code run in the same process. In practice this is often lightweight — the .NET SDK enables logs-to-activity correlation with no extra user code once OpenTelemetry logging is wired up, and Python needs an environment variable (OTEL_PYTHON_LOG_CORRELATION=true) plus adding %(otelTraceID)s/%(otelSpanID)s to the log formatter — but it is always a deliberate integration step. A bare console.log/print call bypasses that pipeline entirely and emits an ordinary line with no trace fields, regardless of which span happens to be active at that moment. This is exactly why 'log-trace correlation' is documented as its own setup step by every OpenTelemetry language SDK and observability vendor, rather than being an automatic side effect of co-located code.

What is 'cardinality' in the context of a metrics system like Prometheus, and why does adding a high-cardinality label — such as user_id, request_id, or a raw unnormalized URL path — to a metric cause production problems at scale?#

Show answer

Cardinality is the number of unique time series a metric produces, which equals the number of distinct combinations of its label values. A metric name plus one specific set of label key/value pairs identifies exactly one time series in the underlying store, and every new unique combination of values creates a brand-new series that has to be indexed and held in memory (and eventually on disk). Bounded labels like status_code or http_method take only a handful of values, so they multiply the series count by a small, known factor. Labels like user_id, session_id, request_id, or a raw URL path are effectively unbounded — a new value for every user, request, or unique path — so attaching one of them to a metric multiplies the series count by that unbounded factor, sometimes into the millions. In production this shows up as memory exhaustion in the time-series database (each series carries a real per-series memory floor), storage blowup, and slower queries because the engine has to scan far more series. Since teams typically respond to hitting that ceiling by dropping metrics or shortening retention, the fix is to keep unbounded-value fields out of metric labels entirely — aggregate them away, or route that data to logs or traces, which are built for high-cardinality point lookups, and reserve metrics for bounded, cheaply-aggregatable dimensions.

Why:

This question separates candidates who've only used a metrics dashboard from those who understand how a time-series database actually stores data. The interview point is the mechanism: a metric's identity is its name plus its full label set, so cardinality is combinatorial — each additional label multiplies the series count by the number of distinct values that label can take, not adds to it. A strong answer names the mechanism (unique label-value combination = new series = new memory/storage cost), gives the canonical bad examples (user_id, request_id, session_id, raw/unnormalized URLs), contrasts them with safe bounded labels (status_code, method, region), and states the production consequence (memory exhaustion, storage growth, slow queries, and the operational trap of teams dropping retention right when they need the history most). The fix — keep unbounded fields out of metric labels and use logs/traces for per-entity lookups — shows the candidate understands why the three observability signals are architecturally different tools, not interchangeable ones.

Why does SRE practice recommend alerting on symptoms rather than on causes, and what's an example of each?#

Show answer

A symptom-based alert fires on something the user actually feels — an elevated error rate, high latency, or an SLO's error budget burning down faster than budgeted — because that's what's worth waking someone up for: it means real, current user-facing damage. A cause-based alert fires on an internal signal that often correlates with a problem but doesn't confirm one — CPU at 80%, disk 70% full, queue depth rising. A cause on its own doesn't establish user impact: a service can run hot on every resource metric all day with zero effect on users if it's still meeting its latency SLO, or it can be actively failing users (e.g. a bad deploy returning fast errors) while every resource metric looks perfectly nominal. Paging primarily on causes floods on-call with alerts that require investigation just to find out whether they matter, which is a leading driver of alert fatigue. The SRE approach: page on symptoms — ideally tied directly to SLIs/SLOs via burn-rate alerts — and keep cause-level metrics around as debugging context to consult once a symptom-based page has already established that something real is wrong.

Why:

This is the 'what vs. why' distinction the SRE monitoring literature treats as one of the most important calls in writing alerts with high signal and low noise: 'what's broken' is the symptom, 'why' is the (possibly intermediate) cause. A strong answer gives the canonical concrete example — a team doesn't alert on 'CPU at 80%,' it alerts when that CPU spike is burning through the monthly error budget faster than expected, because CPU usage is the cause while error-budget burn is the user-facing symptom — and connects the practice back to alert fatigue: cause-based paging generates volume that doesn't reliably indicate user harm, training responders to ignore pages. Weaker answers can define 'symptom vs. cause' abstractly but can't produce a concrete before/after example or connect it to why noisy cause-based alerting burns out on-call.

This Express middleware instruments every HTTP request with a Prometheus counter so the team can graph request volume and error rate per endpoint. After deploying, the Prometheus server's memory usage climbs without bound and eventually OOMs. What's the bug, and what's the fix?#

const httpRequestsTotal = new prom.Counter({
  name: "http_requests_total",
  help: "Total HTTP requests handled",
  labelNames: ["method", "path", "status_code"],
});

app.use((req, res, next) => {
  res.on("finish", () => {
    httpRequestsTotal.inc({
      method: req.method,
      path: req.originalUrl,
      status_code: res.statusCode,
    });
  });
  next();
});

Options

Show answer

path is set to req.originalUrl — the raw request path plus query string, including any embedded IDs like /users/48213/orders/91027?ref=email — so every distinct URL becomes a new label value and the counter grows one time series per unique URL ever hit, an unbounded set; the fix is to label with the matched route template (e.g. req.route.path, which yields /users/:id/orders/:id) and drop the query string, giving the label a small, bounded set of values regardless of traffic volume

Why:

The bug is cardinality explosion, and it's caused by the path label. req.originalUrl is the raw, unnormalized URL — it includes path parameters (/users/48213/...) and the query string, so nearly every request produces a URL that has never been seen before. Because a Prometheus time series is identified by metric name plus its full label set, every one of those distinct path values creates a brand-new, permanent time series that has to be indexed and held in memory — so memory grows roughly linearly with traffic instead of being bounded by the small number of actual endpoints the service exposes. The fix is to label with the matched route template the router resolved the request to (Express exposes this as req.route.path, giving a bounded value like /users/:id/orders/:id regardless of which user or order is requested) and to never include the query string in a label at all. The general rule this illustrates: any field whose value-space scales with users, requests, or time — IDs, session tokens, raw URLs, full timestamps — does not belong in a metric label; that kind of high-cardinality lookup is exactly what logs and traces are architected for, while a metrics store is architected for bounded aggregation. The claim that switching to res.on('close', ...) fixes the unbounded memory growth is a red herring — the finish-vs-close event timing has nothing to do with unbounded label growth. The claim that Prometheus counters leak memory by design and that calling .reset() on the counter on a timer fixes it misdiagnoses the failure as a client-library leak and proposes periodically discarding real data instead of fixing the label; it wouldn't even work, since new unique paths would keep arriving between resets and the underlying cause is untouched. Moving new prom.Counter(...) inside the app.use callback makes it worse: creating a new Counter object per request breaks the entire point of a counter (a single accumulating series) and does nothing about the unbounded path values driving the memory growth.

Related interview questions

Job market

See devops salaries and hiring demand from live job postings.

Practise these until they stick

That's every question we hold on this topic, and the page marks what you pick. What it can't do is remember. A free account keeps every answer, and what you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.