skip to content

In PromQL, what does an instant vector selector return versus a range vector selector, and which functions require each?

level: juniorimportance: must knowfreq 82%

answer

  1. Two shapes of selector
  2. Brackets change the type, not the zoom
  3. Functions dispatch on vector type
  4. A rate needs two samples minimum

basics

~20 s

An instant vector selector returns one sample per series at the query timestamp; a range vector selector adds a bracketed duration and returns every sample in that window. Functions like rate() only accept the bracketed range form.

solid answer

~40 s

An **instant vector** selector such as `hearings_booked_total{courtroom="4"}` returns a single sample per matching series, the most recent value at or before the evaluation timestamp, found within a lookback window of five minutes by default; if nothing was scraped inside that window the series is simply absent rather than zero. A **range vector** selector adds a duration — `hearings_booked_total{courtroom="4"}[5m]` — and returns every sample in that window for each series. Type is what functions dispatch on: `rate`, `irate`, `increase` and the `_over_time` family take range vectors, while aggregation operators such as `sum` and `topk`, arithmetic and comparison take instant vectors. The usual mistake is writing `sum(hearings_booked_total[5m])` or `rate(hearings_booked_total)`, both type errors. A range vector also cannot be graphed directly; something must reduce it to one value per series first.

code

promql · 3 lines
promql
hearings_booked_total{courtroom="4"}
hearings_booked_total{courtroom="4"}[5m]
rate(hearings_booked_total{courtroom="4"}[5m])

go deeper

for a junior

Be ready to state the difference in one sentence and to say which form rate() needs. Knowing that the brackets change the type, not the display window, is the whole point of the question at this level.

for a middle

Explain how the engine picks the sample for an instant selector, why an unscraped series disappears instead of reading zero, and which families of functions consume each type. Naming a range-vector function and an instant-vector operator is expected.

for a senior

Show judgement about window width: how many samples a range yields at a given scrape interval, how many missed scrapes it survives, and what a too-narrow range does to a dashboard during a rolling restart.

for a principal

Own the consequence at fleet scale. The bracketed duration decides how many samples every evaluation loads per series, so a house convention on ranges is simultaneously a correctness policy and a read-path capacity decision.

## Two types, one language Every PromQL expression is evaluated at a single instant in time, called the evaluation timestamp. A **range query** simply repeats that evaluation at fixed steps across a window and stitches the results into a graph, but each individual evaluation still happens at one timestamp. The two vector types describe what a selector hands the rest of the expression at that moment. An **instant vector selector** is a metric name plus optional label matchers, such as `hearings_booked_total{courtroom="4"}`. It returns *one* sample per matching series: the most recent sample at or before the evaluation timestamp. Prometheus looks back a bounded distance for that sample, five minutes by default. If the newest sample of a series is older than that window, the series is not returned at all. It does not read as zero, and it does not carry a stale value forward forever. A **range vector selector** appends a duration in square brackets: `hearings_booked_total{courtroom="4"}[5m]`. It returns *every* sample recorded in the window ending at the evaluation timestamp, for each matching series. One series scraped every 15 seconds yields roughly twenty samples per evaluation instead of one. ## Which functions demand which | Expression | Type | Note | |---|---|---| | `up` | instant vector | one sample per series | | `up[10m]` | range vector | many samples per series | | `rate(up[10m])` | instant vector | consumes a range, emits one value per series | | `sum(rate(up[10m]))` | instant vector | aggregators consume instant vectors | | `time()` | scalar | not a vector at all | - **Range-vector functions** — `rate`, `irate`, `increase`, `delta`, `deriv`, `resets`, `changes` and the whole `_over_time` family such as `avg_over_time`, `max_over_time` and `quantile_over_time` — require the bracketed form. They exist precisely to reduce many samples per series to one. - **Instant-vector functions and operators** — the aggregation operators `sum`, `avg`, `count`, `topk` and `quantile`, arithmetic, comparison, `label_replace`, `absent` and `histogram_quantile` — require a single sample per series. - A **subquery**, written `expr[30m:1m]`, is the one way to build a range vector from an *expression* rather than from a selector. That is why something like `rate(sum(x)[5m:1m])` parses at all, and it is nearly always the wrong query. ## The mistake candidates actually make The single most common error is treating the brackets as "how much history to show" rather than as a change of type. It appears in two symmetrical forms. 1. **A range vector where an instant vector belongs.** `sum(hearings_booked_total[5m])` reads like "sum the last five minutes" and is a type error, because an aggregation operator cannot consume a range vector. The intent "total across series right now" is `sum(hearings_booked_total)`; the intent "sum of the values over time, per series" is `sum_over_time(hearings_booked_total[5m])`. Those are different questions with different answers, and the brackets are what distinguishes them. 2. **An instant vector where a range vector belongs.** `rate(hearings_booked_total)` is a type error too, and the reason is the instructive half: a rate needs at least two samples to subtract, and one sample is all an instant selector ever supplies. The bracketed duration is what provides the second point. A related trap is trying to graph a range vector directly. `hearings_booked_total[5m]` is a legal expression but cannot be rendered as a time series, because at every step it holds many points per series rather than one. Something has to reduce it: a range function, or `last_over_time` when you genuinely want the raw value. ## Why the lookback rule matters in practice Consider a courtroom-scheduling platform whose hearing-booking service runs 41 replicas on a 7-node cluster, scraped every 15 seconds. - An instant query for `up{job="hearing-booking"}` at 09:41:07 does not read values recorded at exactly 09:41:07. It reads each series' most recent sample, which may be up to fifteen seconds old, and it returns nothing at all for a replica that stopped being scraped six minutes ago. - A range vector `[1m]` over the same series returns about four samples each. That is enough for `rate` to work, but only just: two missed scrapes leave a window `rate` cannot use, and those series silently disappear from the result rather than producing an error. - Widening to `[5m]` gives around twenty samples per series and survives several missed scrapes, at the cost of smoothing short spikes. That trade — enough samples to be robust, few enough to stay responsive — is the practical reason the type distinction is worth understanding rather than memorising. The bracket is not decoration. It decides how much data the engine loads per series per evaluation, and therefore both what the answer means and what the query costs.

  • What does an instant query return for a series whose target stopped being scraped ten minutes ago?
    Nothing. The instant vector selector looks back a bounded distance, five minutes by default, and a series with no sample in that window is left out of the result entirely rather than reported as zero or held at its last value. Prometheus also writes an explicit staleness marker when a series disappears from a scrape, so the series stops being returned promptly rather than lingering for the whole lookback window.
  • How would you show the raw last value of a series over a window without computing a rate?
    Use a range-vector function that selects rather than differentiates, such as `last_over_time(x[5m])`, `max_over_time(x[5m])` or `avg_over_time(x[5m])`. They take the bracketed form and return one value per series, so the result is renderable. The raw range vector `x[5m]` is legal but cannot be graphed, because at every step it holds many points per series instead of one.
  • What is a subquery, and why does it exist?
    A subquery, written `expr[30m:1m]`, evaluates an arbitrary expression at a fixed resolution across a window and produces a range vector from it. Range selectors can only be attached to a selector, so a subquery is the only way to feed the output of an aggregation into a range-vector function. It is powerful and expensive, because the inner expression is evaluated once per inner step.

The brackets are not a zoom control on a graph; they change what kind of thing the selector hands to the next function, one photograph per series versus the whole roll of film.

saying these in an interview costs you the question

  • Thinking the brackets control how much history a graph shows
  • Believing an absent series reads as zero in an instant query
  • Writing sum(metric[5m]) to total the last five minutes
  • Calling rate() on a bare selector with no duration
  • Expecting a raw range vector to render as a line