Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Prometheus & Monitoring By James Joyner IV · · 9 min read

Prometheus Error Guide: 'binary expression must contain only scalar and instant vector types' Type Mismatch

Fix PromQL 'binary expression must contain only scalar and instant vector types' errors: wrap range vectors in rate(), use scalar(), and add on()/ignoring() matching.

  • #prometheus-monitoring
  • #troubleshooting
  • #errors
  • #promql

Exact Error Message

This is a PromQL type error. The expression parsed correctly, but the engine refuses to evaluate a binary operation between operands of incompatible types. The Graph/Explore UI shows a red banner; the HTTP API returns a 400 with errorType: "bad_data". Common wordings:

binary expression must contain only scalar and instant vector types
invalid parameter "query": comparisons between scalars and vectors must be done with explicit bool modifier
set operator not allowed in binary scalar expression

The phrase to internalize is scalar and instant vector types: those are the only operand types allowed on either side of + - * / % ^, the comparison operators, and the set operators and/or/unless. A range vector (metric[5m]) is not permitted.

What the Error Means

PromQL has four value types: scalar, instant vector, range vector, and string. Binary operators accept only scalars and instant vectors. The moment one side of an operator is a range vector — or you try a set operator (and/or/unless) inside a scalar context — the type checker aborts.

This is distinct from a parse error: the grammar was satisfied, so the parser built an expression tree. The failure happens during type checking, one stage later, when the engine confirms each operator’s operands are legal. The fix is therefore never about brackets or quoting — it is about converting each operand into a scalar or instant vector before the operator sees it.

Common Causes

1. A range vector left raw in arithmetic

http_requests_total[5m] is a range vector. Putting it in arithmetic without first collapsing it (with rate, increase, avg_over_time, etc.) is the number-one trigger:

http_requests_total[5m] / 60         # range vector in division — illegal

2. Comparing or combining two range vectors

node_network_receive_bytes[5m] > node_network_transmit_bytes[5m]

Both sides are range vectors; neither side is a legal operand.

3. A subquery or function returning the wrong type

avg_over_time(metric) without a range, or forgetting that some functions emit a range while others emit an instant vector, leaves a range vector dangling in an operator.

4. Comparing a vector to a scalar without bool, or needing scalar()

A vector-to-scalar comparison is allowed and filters the vector. But where you actually want a 0/1 numeric result you must add bool, and where you want to multiply a vector by a single number derived from a query you must wrap that query in scalar():

up == 1                              # filters (keeps series equal to 1)
up == bool 1                         # returns 0/1 per series
node_filesystem_avail_bytes / scalar(count(up))   # scalar() needed

5. Vector-to-vector label mismatch (matching needed)

Two instant vectors only combine where their label sets match exactly. If one side has extra labels, the operator silently drops series or errors on cardinality, and you need on()/ignoring():

rate(http_requests_total[5m]) / rate(http_requests_received_total[5m])

If the two metrics carry different label sets, matching has to be made explicit.

6. One-to-many joins needing group_left / group_right

Joining a high-cardinality vector to a low-cardinality one (e.g. attaching a metadata metric like kube_pod_info) requires group_left/group_right to declare the “many” side.

How to Reproduce the Error

Feed a raw range vector into arithmetic:

curl -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=http_requests_total[5m] / 60' | jq -r '.error'
binary expression must contain only scalar and instant vector types

The same via promtool:

promtool query instant http://localhost:9090 'node_cpu_seconds_total[1m] > 5'
query error: ... binary expression must contain only scalar and instant vector types

Diagnostic Commands

Evaluate each side of the operator separately and inspect its resultTypematrix means range vector (illegal in arithmetic), vector means instant vector (legal), scalar is legal:

curl -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=rate(http_requests_total[5m])' \
  | jq -r '.data.resultType'
vector
curl -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=http_requests_total[5m]' \
  | jq -r '.data.resultType'
matrix

When matching is the issue, compare the label sets of each operand to find which labels differ:

curl -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=rate(http_requests_total[5m])' \
  | jq -r '.data.result[0].metric | keys | join(",")'

Then validate the fixed expression over time with query_range:

curl -s 'http://localhost:9090/api/v1/query_range' \
  --data-urlencode 'query=sum(rate(http_requests_total[5m])) / 60' \
  --data-urlencode 'start=2026-06-27T00:00:00Z' \
  --data-urlencode 'end=2026-06-27T01:00:00Z' \
  --data-urlencode 'step=60s' | jq '.status'

Step-by-Step Resolution

  1. Collapse range vectors first. Wrap each metric[5m] in rate()/increase()/avg_over_time() so it becomes an instant vector before the operator:

    rate(http_requests_total[5m]) / 60
  2. Fix both sides if both were ranges:

    rate(node_network_receive_bytes[5m]) > rate(node_network_transmit_bytes[5m])
  3. Use scalar() where one side must be a single number derived from a vector query:

    node_filesystem_avail_bytes / scalar(sum(node_filesystem_size_bytes))
  4. Add bool to comparisons that should yield 0/1 rather than filter:

    (rate(http_requests_total{status=~"5.."}[5m]) > bool 0)
  5. Make vector matching explicit with on()/ignoring() when label sets differ:

    sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
      /
    sum by (job) (rate(http_requests_total[5m]))
    rate(errors_total[5m]) / ignoring(status) rate(requests_total[5m])
  6. Use group_left/group_right for one-to-many joins, e.g. decorating a rate with metadata labels:

    sum by (pod) (rate(container_cpu_usage_seconds_total[5m]))
      * on(pod) group_left(node)
    kube_pod_info
  7. Re-run in Explore. A non-error vector result confirms the type error is resolved.

Prevention and Best Practices

  • Treat rate()/increase() as mandatory the instant you write [5m] for anything you will do math on — never leave a range vector bare.
  • Aggregate both operands to the same label set (sum by (job) (...)) before dividing; matched grouping is the most robust way to avoid mismatch errors.
  • Reserve on()/ignoring()/group_left for genuine metadata joins, and keep the matched label list as small as possible.
  • Test each operand independently in Explore and confirm both show vector (or one scalar) before combining them.
  • The free incident assistant can convert a type-mismatched expression into a correctly typed one with the right matching modifiers; see more under Prometheus and monitoring.

Frequently Asked Questions

What is the difference between an instant vector and a range vector here? An instant vector has one sample per series at the query time; a range vector (metric[5m]) has a window of samples per series. Binary operators accept instant vectors and scalars only — range vectors must be reduced with rate(), increase(), avg_over_time(), etc. first.

When do I need scalar()? When one side of an operator must be a single plain number but your query produces a one-element vector. scalar() converts a single-element instant vector into a scalar; if the vector has zero or more than one element it yields NaN.

Why does up == 1 work but up == bool 1 returns 0/1? Without bool, a vector-to-scalar comparison filters: it keeps only series matching the condition. With bool, it returns every series with a value of 1 (true) or 0 (false). Use bool in alert math; omit it when you want to filter.

How is this different from ‘many-to-many matching not allowed’? That error means both operands are valid instant vectors but have duplicate label sets on the matched labels, so the join is ambiguous. The error in this guide fires earlier, when an operand is the wrong type (a range vector) or a set operator is misused. See the many-to-many guide.

Can I compare two range vectors directly? No. Wrap each in a range function first: rate(a[5m]) > rate(b[5m]). There is no operator that accepts range vectors as operands.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.