The 50 Prometheus questions interviewers actually ask, with direct answers, real PromQL and config snippets, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
50 questions with answersKey Takeaways
Prometheus is an open-source systems monitoring and alerting toolkit. It collects numeric measurements as time series: each sample is a value, a timestamp, and a set of labels that identify what was measured. Prometheus scrapes those metrics by pulling them over HTTP from targets that expose a /metrics endpoint, stores them in its own time-series database on local disk, and lets you query them with PromQL. It also evaluates alerting rules and hands firing alerts to a separate component, Alertmanager, which handles grouping, silencing, and routing to email, Slack, or PagerDuty. The official Prometheus overview documentation is the canonical description of this architecture and the pull-based model. In interviews, Prometheus questions probe how you reason about trade-offs: pull versus push, when a counter needs rate() versus a raw value, how histograms let you compute latency percentiles, and how you'd keep alerts actionable. This page collects the 50 questions that come up most, each with a direct answer plus real PromQL and config. Pair it with our AI interview preparation guides for the live-interview format, since the first DevOps round is increasingly an AI-driven technical screen.
Watch: How Prometheus Monitoring works | Prometheus Architecture explained
Video: How Prometheus Monitoring works | Prometheus Architecture explained (TechWorld with Nana, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your Prometheus certificate.
The fundamentals every entry-level round checks: what Prometheus is, the pull model, metric types, and basic PromQL. If any answer here surprises you, that's your study list.
Prometheus is an open-source monitoring and alerting system. It collects numeric metrics from your services and infrastructure as time series, stores them in its own local time-series database on disk, and lets you query them with PromQL and alert on them. It's built for dynamic, containerized environments where the things you monitor come and go.
Teams use it to answer questions like how many requests per second a service handles, what its error rate and latency look like, and whether a host is running out of memory. It pulls metrics over HTTP, queries them with PromQL, and fires alerts through Alertmanager when something crosses a threshold. It's built for dynamic, containerized environments where targets come and go.
Key point: Lead with 'metrics-based monitoring and alerting', not 'logging'. Interviewers open with this to hear whether you understand Prometheus is about numeric time series, not log storage.
Watch a deeper explanation
Video: How Prometheus Monitoring works | Prometheus Architecture explained (TechWorld with Nana, YouTube)
Prometheus is primarily pull-based: the server scrapes a /metrics HTTP endpoint on each target at a configured interval, rather than targets pushing data to it. You tell Prometheus where the targets are, through static config or service discovery, and it does the pulling on its own schedule.
Pull has real advantages: Prometheus controls the scrape rate, it can tell instantly when a target is down (the scrape fails), and you can point a browser at any target's /metrics to see exactly what it exposes. For short-lived jobs that finish before a scrape can reach them, like a batch job, you push to a Pushgateway, which Prometheus then scrapes. So the default is pull, with a narrow push escape hatch.
Key point: The Pushgateway exception for batch jobs. Saying 'pull, except short-lived jobs push to a Pushgateway' shows you know where the model bends.
The core is the Prometheus server, which scrapes targets, stores the time series, and evaluates rules. Around it sit exporters (like node_exporter) that expose metrics for things that can't expose them natively, and client libraries you use to instrument your own code.
Alertmanager is a separate service that takes firing alerts from Prometheus and handles grouping, silencing, and routing to Slack, email, or PagerDuty. Grafana usually sits on top for dashboards. Service discovery feeds Prometheus the list of targets, and the Pushgateway handles short-lived jobs. Knowing each piece's job, especially that Alertmanager is separate, is the point of this question.
Key point: Stress that Alertmanager is a separate component from the Prometheus server. Candidates who think Prometheus sends the Slack message itself get corrected here.
Counter, gauge, histogram, and summary. A counter only goes up, like total requests or total errors, and resets to zero when the process restarts. A gauge goes up and down and represents a value right now, like memory in use, queue length, or temperature. Those two cover most everyday instrumentation.
A histogram samples observations into configurable buckets, so you can compute percentiles like p95 latency across all instances. A summary is similar but calculates quantiles on the client side, which makes it cheaper to query but impossible to aggregate across instances. The everyday pair is counter and gauge; histograms come up the moment you need latency percentiles.
| Type | Behaviour | Typical use |
|---|---|---|
| Counter | Only increases, resets on restart | Requests total, errors total |
| Gauge | Goes up and down | Memory used, queue depth, temperature |
| Histogram | Buckets observations, server-side quantiles | Latency percentiles across instances |
| Summary | Client-side quantiles | Latency when you can't aggregate |
Key point: Know that histogram quantiles aggregate across instances but summary quantiles don't. That single distinction is the most common histogram-vs-summary follow-up.
A counter is a cumulative metric that only ever increases and resets to zero when the process restarts. Total HTTP requests served is a counter: it climbs forever while the process runs, and its raw value only becomes useful when you turn it into a rate of change with rate().
A gauge is a value that can go up or down, a snapshot of something right now: current memory usage, number of items in a queue, active connections. The practical tell: if the raw number is only useful as a rate of change, it's a counter; if the raw number is meaningful on its own, it's a gauge.
Key point: Give the decision rule: 'meaningful as a rate' means counter, 'meaningful right now' means gauge. That framing shows you can pick the type, not just define both.
A time series is a stream of timestamped values that all share the same metric name and the same set of labels. Each data point in that stream is a sample: a float64 value paired with a millisecond timestamp. Prometheus stores one such series per unique combination of metric name and labels.
The identity of a series is its metric name plus its labels. So http_requests_total{method="GET", status="200"} and http_requests_total{method="POST", status="200"} are two different series, even though they share a name. Every unique label combination is its own series, which is exactly why cardinality matters.
# metric name + labels identify one series
http_requests_total{method="GET", status="200", handler="/api"}
# a different label value = a different series
http_requests_total{method="POST", status="500", handler="/api"}Key point: Say 'metric name plus labels uniquely identify a series'. That definition sets up every cardinality and PromQL question that follows.
Labels are key-value pairs attached to a metric that add dimensions you can filter and group by: method, status code, instance, region. They're what makes Prometheus's data model dimensional instead of flat, because a single metric name can carry many series, one per label combination.
They matter because PromQL queries slice and aggregate across labels: you can ask for the error rate by endpoint, or CPU by instance, without defining a separate metric for each. The catch is cardinality: every distinct label value combination is a new series, so labels must have a bounded, small set of possible values.
Key point: Pair the upside (dimensional queries) with the risk (cardinality) in one breath. Mentioning only one half is what a shallower candidate does.
PromQL (Prometheus Query Language) is the language you use to select and compute over time-series data. You write an expression that returns either an instant vector (one value per series right now), a range vector (a window of samples), a scalar, or a string.
You use it everywhere: in the Prometheus UI to explore, in Grafana panels, in recording rules, and in alerting rules. The everyday patterns are selecting series by label, applying functions like rate() to counters, and aggregating with sum, avg, and max grouped by a label.
# per-second request rate over the last 5 minutes, by status
sum(rate(http_requests_total[5m])) by (status)Key point: The four return types (instant vector, range vector, scalar, string) matters. Knowing an instant vector from a range vector is foundational for later PromQL questions.
An instant vector is a set of series where each has a single sample at one point in time. Writing just http_requests_total gives you an instant vector: the latest value of every matching series at the moment the query runs. It's what most simple expressions return.
A range vector is a set of series where each holds a range of samples over a time window, written with a duration in brackets like http_requests_total[5m]. You can't graph a range vector directly; you feed it to a function like rate() or increase() that turns it back into an instant vector. That's why counters look like rate(metric[5m]), not metric[5m] alone.
Key point: Explain why rate() needs a range vector: it needs multiple samples to compute a per-second increase. That connection is the real point of the question.
rate() takes a range vector of a counter and returns the per-second average rate of increase over that window. So rate(http_requests_total[5m]) gives you requests per second, averaged over the last five minutes, which is the number you actually want on a dashboard rather than the ever-climbing raw total.
You use it because a raw counter only climbs and its absolute value is meaningless, what you care about is how fast it's growing. rate() also automatically handles counter resets: when a process restarts and the counter drops to zero, rate() detects it and doesn't report a huge negative spike. That reset handling is a big reason you never subtract counter values by hand.
# requests per second, handles restarts correctly
rate(http_requests_total[5m])
# total count over the window (not per-second)
increase(http_requests_total[5m])Key point: Mention that rate() handles counter resets automatically. That detail proves you understand why manual counter subtraction is wrong.
rate() computes the average per-second rate across the whole range window, smoothing out short spikes into a stable line. irate() uses only the last two data points in the window, so it reacts fast to sudden changes but is jumpy and noisy. Same idea, opposite sensitivity to short-term movement.
The practical guidance: use rate() for alerting and most dashboards because its smoothing gives stable, meaningful values, and use irate() only for graphing fast-moving, volatile signals where you want to see the spikes. Using irate() in an alert is a common mistake because its jumpiness causes flapping.
Key point: Say 'rate() for alerts, irate() only for volatile graphs'. The trap answer is defaulting to irate() everywhere, which causes flapping alerts.
An exporter is a small process that sits next to something Prometheus can't scrape directly, a database, a Linux host, a message queue, and translates that system's internal stats into the Prometheus metrics format on a /metrics endpoint. Prometheus then scrapes the exporter as if it were any other target.
node_exporter is the classic example: it exposes CPU, memory, disk, and network metrics for a Linux host. There are exporters for MySQL, Redis, Kafka, blackbox probing, and hundreds more. You reach for one whenever the thing you want to monitor doesn't natively expose Prometheus metrics.
Key point: Give node_exporter as the canonical example and explain it exposes host metrics. That concrete example lands better than an abstract definition.
node_exporter runs on a Linux (or Unix) host and exposes hardware and OS-level metrics: CPU usage, memory, disk space and I/O, filesystem, network stats, and load average. Prometheus scrapes it to monitor the machine itself, separate from whatever application happens to be running on that machine.
It's usually the first exporter people install because host-level metrics underpin almost everything. You run one per host, Prometheus discovers them, and then you build alerts like low disk space or high memory from the metrics it exposes.
Key point: Note it runs one-per-host and covers OS/hardware metrics, not application metrics. Confusing it with app instrumentation is a common junior slip.
The /metrics endpoint is an HTTP path a target exposes where Prometheus scrapes its current metric values. It returns plain text in the Prometheus exposition format: one line per sample, each with the metric name, its labels, and the value, plus optional HELP and TYPE comment lines describing each metric.
Because it's just HTTP text, you can open it in a browser or curl it to see exactly what a service is exporting, which makes debugging simple. When Prometheus scrapes a target, it does an HTTP GET on this endpoint and parses every line into samples.
# what a /metrics response looks like
# HELP http_requests_total Total HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 1027
http_requests_total{method="POST",status="500"} 3
process_resident_memory_bytes 5.242e+07Key point: Mention you can curl /metrics to debug. Knowing the endpoint is human-readable text shows hands-on familiarity, not just theory.
The scrape interval is how often Prometheus pulls metrics from each target, commonly 15 or 30 seconds. You set a global default in prometheus.yml and can override it per scrape job, so noisy fast-moving targets can be scraped more often than slow, stable ones.
It's a trade-off: a shorter interval gives finer resolution and faster detection but more storage and load; a longer one is cheaper but blurs short spikes. A rule of thumb is that your rate() window should span at least a few scrape intervals, so a 15s scrape pairs with something like rate(metric[1m]) or wider.
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: node
scrape_interval: 30s # override for this job
static_configs:
- targets: ["host1:9100", "host2:9100"]Key point: scrape interval connects to rate() windows: the window should cover several intervals. That link shows you understand why they're not independent settings.
prometheus.yml is the main config. It holds global settings (default scrape and evaluation intervals), the scrape_configs that define what to scrape and how to discover targets, rule_files pointing at your recording and alerting rules, and the alerting block that tells Prometheus where Alertmanager is.
You reload it without a full restart by sending SIGHUP or hitting the /-/reload endpoint (with reloading enabled), which is how you add a scrape job or rule file in production without downtime.
global:
scrape_interval: 15s
rule_files:
- "rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]Key point: Mention hot-reload via /-/reload or SIGHUP. Knowing you don't need a restart to add a scrape job signals real operational experience.
Prometheus collects, stores, and alerts on metrics, and answers PromQL queries. Grafana is a visualization tool: it queries Prometheus (and other data sources) and draws dashboards, but it doesn't store metrics or scrape anything itself. Think of Prometheus as the engine and Grafana as the dashboard bolted on top of it.
They're complementary, not competitors. Prometheus is the data source and the alerting brain; Grafana is the pretty front end that makes the data readable. Prometheus has a basic built-in UI for exploring queries, but for real dashboards teams point Grafana at it.
Key point: Be clear Grafana stores no data and does no scraping. Treating them as alternatives instead of layers is the mistake this question catches.
Nagios is check-based: it runs scripts on a schedule that return an OK, WARNING, or CRITICAL state for a host or service, so it's built around up-or-down status. Prometheus is metrics-based: it collects numeric time series and lets you query and alert on any dimension of them with PromQL, not just a pass or fail state.
The practical difference is what you can ask afterward. Nagios tells you a check failed; Prometheus keeps the underlying numbers so you can graph trends, compute rates and percentiles, and slice by label. Nagios suits simple static host checks; Prometheus suits dynamic infrastructure where you want rich, queryable metrics and dimensional alerting.
Key point: Frame it as check-based state versus queryable numeric time series. Saying Prometheus keeps the raw numbers, so you can ask new questions later, is the distinction that lands.
For candidates with working experience: PromQL aggregation, alerting, service discovery, storage, and the judgment questions that separate tool users from operators.
Aggregation operators (sum, avg, max, min, count, and others) collapse many series into fewer. By default they collapse everything into one value. by (label) keeps the listed labels and aggregates away the rest; without (label) drops the listed labels and keeps the rest.
So sum(rate(http_requests_total[5m])) by (status) gives you one requests-per-second line per status code, summed across every instance and endpoint. without is handy when you want to keep most labels and only strip a noisy one like instance. Picking the right grouping is most of writing useful PromQL.
# total request rate, grouped by status code
sum(rate(http_requests_total[5m])) by (status)
# same, but keep every label except instance
sum(rate(http_requests_total[5m])) without (instance)Key point: Explain by keeps labels and without drops them. Mixing those up is the single most common PromQL aggregation error interviewers watch for.
You use a histogram metric plus the histogram_quantile() function. The histogram exposes _bucket series, which are cumulative counts of observations per latency bucket, and histogram_quantile(0.95, rate(..._bucket[5m])) estimates the 95th percentile from those buckets. You can't get a real percentile from a plain average or a raw counter.
Two things trip people up. First, you apply rate() to the buckets before histogram_quantile(), because you want the recent distribution, not the all-time one. Second, the result is an estimate whose accuracy depends on your bucket boundaries, so if your buckets are coarse near your real latency, the percentile is fuzzy. You can't get a true percentile from a plain average or a counter.
# approximate p95 latency, grouped by endpoint
histogram_quantile(
0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, handler)
)Key point: Say the result is an estimate that depends on bucket layout, and that you rate() the buckets first. Both are follow-ups that separate real histogram users from readers.
Choose a histogram when you need to aggregate percentiles across many instances, which is almost always in a distributed system. Histograms expose raw buckets, so you can sum buckets from every instance and then compute a percentile over the whole fleet with histogram_quantile().
Summaries compute quantiles on the client side, so they're cheaper to query per instance but you cannot meaningfully average or combine those quantiles across instances. So summaries fit a single-instance case or when you want an exact client-side quantile and never need to aggregate. In practice most teams default to histograms for that aggregation ability.
| Histogram | Summary | |
|---|---|---|
| Quantile computed | Server-side at query time | Client-side ahead of time |
| Aggregatable across instances | Yes | No |
| Cost | More storage (buckets) | More client CPU |
Key point: The deciding factor is 'do you need to aggregate across instances'. If yes, histogram. Reciting definitions without that decision rule reads shallow.
An alerting rule is a PromQL expression plus a for duration. When the expression returns results continuously for longer than for, the alert transitions from pending to firing, and Prometheus sends it to Alertmanager. Labels on the rule set severity and routing; annotations carry the human-readable description.
The for clause is what stops flapping: requiring the condition to hold for, say, five minutes filters out momentary blips. Prometheus only evaluates the rule and decides firing versus not; everything after, grouping, silencing, notifying, is Alertmanager's job.
groups:
- name: availability
rules:
- alert: HighErrorRate
expr: sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: page
annotations:
summary: "5xx error rate above 5% for 5 minutes"Key point: Explain the for clause as the anti-flapping control. Candidates who omit for and alert on a single spike get pushed on why their pager never stops.
Alertmanager receives firing alerts from one or more Prometheus servers and handles everything about turning them into notifications. It groups related alerts into one notification (so a node failure doesn't send fifty separate pages), deduplicates identical alerts from HA Prometheus pairs, and routes each alert to the right receiver based on labels.
It also does silencing (mute alerts during known maintenance) and inhibition (suppress lower-priority alerts when a higher-priority one is already firing, like not paging for pod alerts when the whole cluster is down). Then it delivers to receivers: email, Slack, PagerDuty, webhooks.
Key point: The grouping, silencing, and inhibition specifically. Listing all three shows you've configured Alertmanager, not just heard the name.
Service discovery keeps the target list current automatically instead of you hardcoding IPs. Prometheus has built-in discovery for Kubernetes, Consul, EC2, Azure, GCE, DNS, and more; it queries that system's API, gets the current set of instances, and scrapes them, adding and removing targets as things change.
In Kubernetes, for example, kubernetes_sd_configs discovers pods, services, and endpoints, and you use relabeling to decide which ones to scrape and what labels to attach. This is what makes Prometheus work in autoscaling, ephemeral environments where a static target list would be stale within minutes.
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"Key point: service discovery maps to dynamic infrastructure: static targets go stale as pods churn. That 'why' is what the question is really checking.
Watch a deeper explanation
Video: Setup Prometheus Monitoring on Kubernetes using Helm and Prometheus Operator | Part 1 (TechWorld with Nana, YouTube)
Relabeling rewrites, filters, or drops labels on targets and samples before they're stored. relabel_configs runs at discovery time to decide which targets to scrape and to shape their labels, while metric_relabel_configs runs after the scrape to drop or rewrite specific metrics you've already pulled.
Common uses: keep only targets with a scrape annotation, map a discovery meta-label into a clean job or instance label, or drop a high-cardinality metric you don't want to store. Relabeling is how you turn raw, messy service-discovery metadata into the tidy labels your queries rely on.
Key point: Distinguish relabel_configs (at discovery) from metric_relabel_configs (after scrape). Knowing you can drop a noisy metric post-scrape is a strong cardinality-control signal.
A recording rule evaluates a PromQL expression on a schedule and saves the result as a brand-new time series. Instead of recomputing a heavy aggregation every time a dashboard loads, you compute it once per evaluation interval and query the cheap precomputed series.
You use them for expensive or frequently reused queries: a fleet-wide error ratio, a p99 that feeds several dashboards and alerts. The naming convention is level:metric:operation, like job:http_requests:rate5m. The payoff is faster dashboards and consistent numbers, since every consumer reads the same precomputed value.
groups:
- name: http
interval: 30s
rules:
- record: job:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (job)Key point: The level:metric:operation naming convention matters. It signals you've maintained a real rule set, not just read about recording rules.
Prometheus writes samples to a local time-series database. Incoming data first lands in an in-memory head block and a write-ahead log (the WAL) for crash recovery. Every couple of hours the head is flushed to an immutable on-disk block covering a time range, and older blocks are compacted together over time.
Each block holds the samples plus an index for fast label lookups. Retention is time or size based (for example keep 15 days), after which old blocks are deleted. It's designed for one server's local data; there's no built-in clustering, which is why long-term or global storage means adding remote write.
Key point: The WAL and time-based blocks. Knowing the head block flushes to immutable blocks shows storage understanding beyond 'it saves to disk'.
You set retention with the storage.tsdb.retention.time flag, for example 30d, or storage.tsdb.retention.size to cap disk usage. Prometheus deletes whole on-disk blocks once they fall outside the limit; it doesn't trim individual samples inside a block, so retention is enforced at block granularity, not per sample.
Local retention is deliberately short in most setups (days to a few weeks) because a single server's disk is finite and high-resolution data gets expensive fast. For months or years of history, you don't crank local retention up, you send data to remote storage like Thanos, Cortex, or Mimir that's built for long-term, downsampled retention.
prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=100GB \
--config.file=prometheus.ymlKey point: Say long-term history belongs in remote storage, not a giant local retention. Proposing 2-year local retention on one server is the wrong instinct here.
Remote write streams every sample Prometheus ingests to an external system in near real time, on top of storing it locally. You configure a remote_write endpoint in prometheus.yml and Prometheus forwards data there as it scrapes, buffering in a queue so a slow remote backend doesn't stall local collection.
You need it for long-term storage, global querying across many Prometheus servers, or a managed backend. Systems like Thanos (via sidecar or receive), Cortex, Mimir, and various hosted platforms accept remote write, add durable long-term storage, downsampling, and a single query view across clusters. Local Prometheus stays the collector; the remote system owns the history and the global picture.
Key point: Frame remote write as the bridge to long-term and global storage. Naming Thanos, Cortex, or Mimir shows you know the real-world scaling path.
Cardinality is the number of distinct time series. Because every unique label combination is its own series, putting an unbounded value in a label, a user ID, an email, a full URL with query params, a request ID, multiplies series explosively and can blow up Prometheus memory and query time.
You control it by never labeling with unbounded values: bucket or omit them, use fixed enum-like labels, and drop offending metrics with metric_relabel_configs. Monitor it with prometheus_tsdb_head_series and count by metric name to find the worst offenders. High cardinality is the number-one way people accidentally take Prometheus down.
# find which metrics have the most series
topk(10, count by (__name__)({__name__=~".+"}))
# total active series in the head block
prometheus_tsdb_head_seriesKey point: Give a concrete bad label (user ID, request ID) and the fix (drop it, bucket it). Abstract cardinality talk without a real example reads like textbook recall.
The Pushgateway is a small service that short-lived jobs push their metrics to, and that Prometheus then scrapes. Because a batch job may finish before any scrape reaches it, it pushes its final metrics to the Pushgateway, which holds them for Prometheus to pull.
Use it only for service-level batch jobs, a nightly ETL, a cron task. Don't use it for long-running services (those should expose /metrics and be scraped) and don't treat it as a general push target, because it doesn't track up/down for you, holds the last pushed value indefinitely until deleted, and becomes a single point of aggregation. Overusing it is a known anti-pattern.
Key point: Stress it's for short-lived batch jobs only, and that it holds stale values. Reaching for the Pushgateway as a general push endpoint is the anti-pattern this question probes.
up is a metric Prometheus generates itself for every scrape: it's 1 if the target was scraped successfully and 0 if the scrape failed. You don't instrument it anywhere; Prometheus creates one up series automatically per target, so you always have it even before you add any application metrics.
It's the simplest and most important health signal you have. up == 0 means a target is down or unreachable, so a basic InstanceDown alert is just up == 0 held for a few minutes. Because pull lets Prometheus know immediately when a scrape fails, up gives you instant target-health detection for free.
# targets that are currently down
up == 0
# alert expression
# expr: up == 0
# for: 5mKey point: Point out up is auto-generated and is the basis of the classic InstanceDown alert. It's a favorite because it ties the pull model to health detection.
Federation lets one Prometheus server scrape a selected set of aggregated metrics from another Prometheus server, through a special /federate endpoint. A higher-level Prometheus pulls a filtered slice of series from several lower-level ones, so you can build a summarized cross-cluster view without copying every raw series upward.
The classic use is hierarchical: per-datacenter Prometheus servers collect everything locally, and a global Prometheus federates just the aggregated, lower-cardinality metrics for a cross-datacenter view. It's meant for pulling summaries, not copying every raw series. For full global storage and querying, Thanos or Cortex-style remote-write systems fit better than heavy federation.
Key point: Clarify federation is for aggregated summaries, not full replication. Suggesting you federate every raw series is the misuse the key signal is.
The blackbox exporter probes endpoints from the outside over HTTP, HTTPS, TCP, ICMP, or DNS, and reports whether the probe succeeded, how long it took, and details like the TLS certificate expiry. It's black-box monitoring: you test the service the way a user would, without instrumenting it.
Prometheus scrapes the blackbox exporter with a target URL as a parameter, and the exporter does the probe and returns metrics like probe_success and probe_duration_seconds. It's how you build uptime checks, latency-from-outside checks, and certificate-expiry alerts for endpoints you don't control or can't instrument.
Key point: Contrast black-box (probe from outside) with white-box (instrumented internals). Knowing probe_success and cert-expiry checks come from here shows real usage.
Watch a deeper explanation
Video: Prometheus Monitoring: monitor third-party apps using an Exporter | Part 2 (TechWorld with Nana, YouTube)
When a target disappears or stops exposing a series, Prometheus marks that series stale rather than carrying the last value forever. It inserts a staleness marker, and after that the series returns no value for instant queries instead of a frozen old number.
This matters for correctness: without staleness handling, a gauge for a pod that vanished would look like it's still reporting its last value, and alerts could evaluate on data that's really gone. Staleness is why a query for a scaled-down instance correctly returns nothing shortly after it disappears.
Key point: Explain staleness prevents alerting on the frozen last value of a vanished series. That correctness angle is what the question is testing, not the mechanism alone.
You add Prometheus as a data source in Grafana, then build dashboards where each panel runs a PromQL query against Prometheus. Grafana handles the visuals, the time-range picking, template variables, and layout, while Prometheus does the storing and querying underneath. Neither one duplicates the other's job.
A typical setup: Prometheus scrapes targets and evaluates alerts, Grafana points at it for dashboards, and you reuse community dashboards (like the node_exporter one) as a starting point. Alerting can live in Prometheus and Alertmanager, in Grafana, or both, so a common interview follow-up is where you put alerting and why.
Key point: Have an opinion on where alerting lives (Prometheus/Alertmanager vs Grafana). The follow-up almost always goes there, and 'it depends, here's my default' beats a blank.
Watch a deeper explanation
Video: Server Monitoring with Prometheus and Grafana Tutorial (Christian Lempa, YouTube)
advanced rounds probe scale, reliability, and production scars. Expect every answer here to draw a follow-up about trade-offs and failure modes.
A single Prometheus is vertically scaled and has no built-in clustering, so scaling means partitioning and layering. First shard by scaling out: run multiple Prometheus servers, each scraping a subset of targets (by team, region, or function), so no one server carries everything.
For a global view and long-term storage on top of that, add a system built for it: Thanos (sidecars plus a query layer and object-storage backend) or Cortex/Mimir (remote write into a horizontally scalable backend). These give cross-shard querying, downsampling, and durable history. The mental model is: Prometheus stays the local collector, and a separate layer handles global querying and retention.
Key point: Lead with 'shard the scraping, then add Thanos or Mimir for global and long-term'. Claiming Prometheus itself clusters is a red flag; it doesn't.
Both give Prometheus long-term storage, a global query view across many servers, and downsampling, using cheap object storage. The difference is architecture. Thanos bolts onto existing Prometheus servers with a sidecar that ships blocks to object storage, plus a query component that fans out across sidecars and stored blocks.
Cortex and Mimir take Prometheus data via remote write into a horizontally scalable, multi-tenant service with its own ingesters and queriers, so Prometheus becomes a thin forwarder. Rough guidance: Thanos suits an existing fleet of Prometheus servers you want to unify with minimal change; Mimir/Cortex suit a centralized, multi-tenant platform built around remote write. Both are valid; the fit depends on your starting point.
| Thanos | Cortex / Mimir | |
|---|---|---|
| Integration | Sidecar on existing Prometheus | Remote write into a central service |
| Prometheus role | Still stores locally | Thin forwarder |
| Best fit | Unify an existing fleet | Central multi-tenant platform |
Key point: Anchor the choice in your starting point: existing fleet leans Thanos, central platform leans Mimir. A one-is-better answer misses that it's context-dependent.
The standard pattern is to run two identical Prometheus servers scraping the same targets with the same config. They collect the same data independently, so if one dies the other keeps going. Alertmanager deduplicates the resulting duplicate alerts, so you don't get paged twice.
The catch is that the two servers aren't perfectly in sync (scrapes land at slightly different times), so their data isn't byte-identical. For a consistent global view across the HA pair and over time, you put a layer like Thanos in front, which deduplicates overlapping samples at query time. HA covers alerting continuity; Thanos covers a unified query experience.
Key point: Say Alertmanager dedupes the alerts and a query layer dedupes the data. Knowing the two servers drift slightly, and how that's reconciled, is the senior detail.
Alert on symptoms and user impact, not on causes. A spiking error rate or latency SLI breach always matters; 90% CPU often doesn't. Every alert should mean a human needs to act now, point at the problem, and link to a runbook, otherwise people learn to ignore the pager and miss the real one.
Concretely: use a for window so a momentary blip doesn't page, alert on SLO burn rate rather than every threshold cross, split severities so only real emergencies page (the rest go to a ticket or dashboard), and delete alerts that never lead to action. Fatigue is a failure mode you design against, not an accident.
Designing a good alert
Symptom-based alerting with a for window and a runbook is what keeps a pager actionable instead of noisy.
Key point: Say 'alert on symptoms, use burn-rate, route non-urgent to tickets'. Alerting on raw CPU and a hair-trigger threshold is exactly the fatigue-causing pattern they're screening out.
Burn-rate alerting fires based on how fast you're consuming your error budget rather than on a fixed error-rate threshold. If your SLO is 99.9%, your budget is 0.1% of errors; the burn rate is how quickly you're spending it relative to the window. A high burn rate over a short window means you'll exhaust the budget fast, so it pages; a slow burn over a long window is a ticket.
It's better because a static threshold is either too twitchy (pages on a harmless one-minute blip) or too slow (misses a sustained smaller degradation). Multi-window multi-burn-rate alerts (a fast burn on a short window plus a slow burn on a long one) catch both severe-and-sudden and mild-and-sustained problems while keeping false pages low.
Key point: Mention multi-window multi-burn-rate alerts. Naming that pattern signals SRE-level alerting maturity, which is exactly what this senior question tests.
Memory scales mostly with the number of active series (cardinality) and the query load, so start there. Check prometheus_tsdb_head_series for total active series, and use topk(10, count by (__name__)({__name__=~".+"})) to find which metrics or which labels are exploding the series count. A sudden jump usually means a new deploy added an unbounded label.
Fixes: drop the offending high-cardinality metric or label with metric_relabel_configs, fix the instrumentation so it stops using unbounded label values, and rein in expensive ad-hoc queries or fat recording rules. If the fleet is legitimately large, shard the scraping across more Prometheus servers. The root cause is almost always cardinality, not raw ingestion volume.
# biggest offenders by series count
topk(10, count by (__name__)({__name__=~".+"}))
# series churn over time can also bloat memory
rate(prometheus_tsdb_head_series_created_total[5m])Key point: Go straight to cardinality as the usual cause and The diagnostic queries. Blaming ingestion volume without checking series count is the junior instinct here.
Both detect resets: if a sample is lower than the previous one, they assume the counter restarted (a process restart) and treat the drop as a reset rather than a negative rate, extrapolating across it. That's why you never manually subtract two counter values, you'd get a huge wrong negative when a restart happened mid-window.
Where it goes wrong: increase() and rate() extrapolate to the window edges, so over short windows or with sparse data they can report slightly fractional or inflated counts, which surprises people expecting exact integers. And if a counter resets twice within one scrape interval, the reset can be missed. The takeaway is that these functions are estimates designed for rates, not exact event counts.
Key point: Note that increase() extrapolates and can return non-integers. That subtlety proves you've actually debugged surprising rate() numbers in production.
Precompute the expensive, widely reused aggregations once per evaluation interval and store them as new series, so dashboards and alerts read a cheap single series instead of re-scanning millions of samples on every load. A fleet-wide p99 latency or an error ratio that feeds ten panels is a prime candidate.
At scale you layer them: instance-level rules feed job-level rules feed global rules, so each level reads the precomputed one below it rather than raw data. Follow the level:metric:operation naming so consumers know what they're reading, keep the evaluation interval sane, and watch that the rules themselves don't become the expensive thing. Done right, recording rules turn a dashboard that times out into one that loads instantly.
Key point: Describe layering rules (instance to job to global). That hierarchical approach is what separates someone who's scaled a rule set from someone who's written one rule.
Pull's wins: Prometheus controls scrape timing and rate, it detects a dead target instantly via a failed scrape (the up metric), targets don't need to know where Prometheus is, and you can manually curl any /metrics endpoint to debug. It fits dynamic, discoverable infrastructure cleanly.
Where pull struggles: short-lived jobs that die before a scrape (handled by Pushgateway), targets behind NAT or firewalls that Prometheus can't reach, and event-style data that doesn't fit a periodic scrape. Push shines for those, which is why OpenTelemetry and remote-write pipelines exist alongside pull. The honest answer is pull for most infra monitoring, push for ephemeral or hard-to-reach sources.
Key point: Give concrete cases where pull breaks (batch jobs, NAT, firewalls) and the push escape hatches. A pull-is-always-best answer misses the nuance seniors are expected to hold.
An exemplar is a specific example attached to a histogram bucket sample: it records a trace ID (and the observed value) for an individual request that landed in that bucket. So a latency histogram doesn't just tell you p99 is high, it can hand you a trace ID of an actual slow request.
That closes the gap between metrics and traces: you notice a latency spike on a metric, then jump straight to an exemplar's trace to see where that specific request spent its time. It needs client-library support to emit exemplars and a tracing backend to open them in, and Grafana can link the two. It's the practical bridge from aggregate metric to one real request.
Key point: Frame exemplars as the jump from an aggregate metric to a single real trace. Knowing they carry trace IDs on histogram buckets signals current observability literacy.
Prometheus exposes its own metrics on /metrics, so you scrape it like any target. The signals that matter: prometheus_tsdb_head_series for cardinality, prometheus_rule_evaluation_failures_total and prometheus_rule_group_last_duration_seconds for rules taking too long, scrape health via up and scrape_duration_seconds, and remote-write queue metrics if you forward data.
You also watch resource pressure (memory, WAL size, disk) and whether scrapes are being missed. In an HA setup, two Prometheus servers can watch each other, and a meta-monitoring Prometheus or the Alertmanager's own health checks close the loop, because the thing that pages you can't be the only thing watching itself.
Key point: Raise the meta-monitoring problem: the alerting system can't be its own only watcher. That reasoning is the production insight beyond listing self-metrics.
Clarify first: what does healthy mean for this service, what SLOs matter, what's the traffic, and does it already expose metrics. Then instrument the four golden signals with a client library: request rate and errors (counters), latency (a histogram for percentiles), and saturation (gauges for queue depth, connections, memory). Expose them on /metrics and let service discovery pick the pods up.
Next, add recording rules for the reused aggregations (error ratio, p99), write symptom-based alerts with for windows and runbook links, and route them through Alertmanager with the right severities. Build a Grafana dashboard from those metrics, add a blackbox probe for external uptime, and decide retention (short local plus remote write if you need history). The structure that scores is clarify, instrument the golden signals, aggregate with rules, alert on symptoms, visualize, each step justified by the SLOs you asked about.
# error ratio (a recording-rule candidate)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))
# p99 latency from the histogram
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))Key point: Open by asking about SLOs before instrumenting, and The golden signals. Jumping to a metric list without clarifying what healthy means is the common way to underperform on a design prompt.
OpenMetrics is a standardized specification for the exposition format Prometheus popularized, turning that text format into a vendor-neutral standard so any system can expose or consume metrics the same way. Prometheus supports it, and it adds things like native support for exemplars and clearer typing.
The point is interoperability: instrument once in the OpenMetrics format and multiple backends can scrape it, not just Prometheus. In practice most Prometheus users already emit a compatible format, so OpenMetrics is more about a shared industry standard and features like exemplars than a break from what you already do.
Key point: Frame OpenMetrics as the standardized version of the Prometheus format, with exemplars as a headline feature. That connects it to the tracing story from earlier.
It's built for numeric metrics, not logs or events, and not for long-term high-resolution storage out of the box; a single server doesn't cluster and its local retention is finite. It's also not the tool for billing-grade exact counts, since rate() and increase() are estimates, or for high-cardinality per-request data like individual user IDs.
So you'd reach elsewhere when you need log search (Loki, Elasticsearch), request-level tracing (Jaeger, Tempo), exact event accounting, or a fully managed no-ops metrics platform. For long-term and global metrics you keep Prometheus but add Thanos or Mimir. Being able to name where it doesn't fit is what separates someone who's operated it from someone who treats it as a hammer for everything.
Key point: Volunteering honest limitations (no logs, estimates not exact counts, cardinality limits) is a strong production signal. Claiming Prometheus does everything is the answer that fails this one.
Prometheus fits a specific slot: pull-based, metrics-first, dimensional, and built for dynamic infrastructure where targets come and go. It's not a logs tool and it's not built for long-term high-cardinality storage out of the box. Knowing where it fits against push-based systems like Graphite, check-based systems like Nagios, and hosted metrics platforms like Datadog, and saying the trade-off out loud, is itself an interview signal.
| Tool | Collection model | Data model | Best at |
|---|---|---|---|
| Prometheus | Pull over HTTP | Dimensional labels, PromQL | Dynamic infra, metrics, alerting |
| Graphite | Push (StatsD-style) | Dotted metric paths | Simple push metrics, long history |
| Nagios | Active/passive checks | Host and service states | Up/down checks, legacy hosts |
| Datadog | Agent push, hosted | Tagged metrics, SaaS | Turnkey, logs plus metrics, no ops |
| InfluxDB | Push (line protocol) | Tags and fields | Event and IoT time series, SQL-like |
Prepare in layers, and trade-offs out loud is the explanation path. Most Prometheus rounds move from concept questions to writing PromQL to a scenario or design discussion, so rehearse each stage rather than only reading answers.
The typical Prometheus interview flow
Earlier rounds increasingly run as AI-driven technical interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Prometheus questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works