TL;DR: Histogram, because summaries compute quantiles per instance and quantiles cannot be averaged. A histogram ships bucket counts, which are additive, so you can compute a p99 across a whole fleet at query time. A summary provides an approximate per-instance quantile; it remains useful for that instance but cannot produce a fleet quantile by averaging.
How to approach it
Answer the question directly, then explain the aggregation property, because that is the whole reason and every other difference between the two follows from it.
A strong answer
The four types:
Counter only increases, or resets to zero on restart. Request totals, errors, bytes processed. You almost never graph the raw value; you graph rate() over it, and the function is reset-aware so a process restart does not produce a negative spike.
Gauge goes up and down. Queue depth, memory in use, active connections, temperature. rate() is meaningless on a gauge; you use the value itself, or delta().
Histogram counts observations into cumulative buckets, and ships the bucket counts plus a sum and a total count. Latency, response sizes.
Summary calculates quantiles inside the client and ships the computed values.
Now the distinction that matters. A summary's p99 is an estimate with the client's configured quantile error, and there is no mathematically valid way to combine the p99 of ten instances into a fleet p99. Averaging them is wrong, and the size of the error is worth seeing rather than asserting.
Worked example: what averaging ten p99s actually reports
Ten instances behind a load balancer, each taking 1,000 requests per second, so 10,000 across the fleet. Nine are healthy with a p99 of 50ms. One has a failing disk and a p99 of 2,000ms. These figures are illustrative.
Average the ten reported p99 values and you get (9 × 50 + 2,000) / 10 = 2,450 / 10 = 245 milliseconds. That number will not alarm anyone against a 300ms objective, and it is not a percentile of anything: no request took 245ms because that value was computed from summary statistics rather than from observations.
Now the figure you actually wanted, the 99th percentile across all 10,000 requests. The slowest one percent of the fleet is 10,000 × 0.01 = 100 requests. The degraded instance alone contributes 1,000 requests a second, which is ten times that, so the fleet's slowest 100 requests can all come from it. Those 100 are the slowest 100 / 1,000 = 10 percent of that instance's traffic, which means the fleet p99 sits at roughly that instance's p90. If its p90 is 1,600ms, the fleet p99 is about 1,600ms.
So the average reported 245ms where the truth was near 1,600ms, understating the tail by a factor of about six and a half, and it did so in the direction that hides the incident. The dilution is structural rather than unlucky: one instance in ten can only move an average of ten numbers by a tenth of its own excess, while it can hold the entire slowest one percent of the fleet.
A histogram avoids this because bucket counts are observations and observations add. Sum each bucket across all ten instances, then compute the quantile once over the combined counts, and the degraded instance's 1,000 slow requests land in the high buckets where they belong instead of being averaged away.
Two limits on that worked example. It assumes roughly equal traffic per instance, and an unbalanced load balancer changes the shares. And it treats the degraded instance's distribution as known, whereas in practice you would read it from the histogram rather than assume a p90.
Since almost every service runs more than one replica, and fleet p99 and per-instance p99 answer different questions, summaries are the wrong tool for anything you will aggregate.
A histogram ships counts per bucket, and counts add. Sum the buckets across every instance and compute the quantile at query time with histogram_quantile(), and the result is a genuine fleet-wide figure. The cost is that the answer is approximate, bounded by bucket width, and that you must choose the buckets in advance. Choose them around your SLO: if the target is 300ms, you want boundaries clustered there rather than the defaults, because a quantile falling in a wide bucket is interpolated and the error is the bucket's width.
Cardinality is the other reason to be careful, and the count is worth doing rather than quoting. A histogram creates one series per bucket per label combination. Ten finite bucket boundaries give 10 + 1 = 11 bucket series, because +Inf is always present as the catch-all, and then _sum and _count add two more: 11 + 2 = 13 series for every labelset, before any optional extras the client exports. Multiply that by your label combinations rather than by your bucket count, since it is the labelsets that multiply. Native histograms, which store a sparse exponential representation instead of fixed buckets, reduce per-bucket series overhead, but resolution, bucket limits and interoperability still need configuration, and they are the direction the ecosystem is moving.
The rule I would state: counter for things that happen, gauge for things that are, histogram for things that take time or have size, and summary when a per-instance estimate is useful and you do not need to merge those quantiles.
What interviewers probe next
"Why not just average the latency?" An average hides the tail, which is the part users experience. It also cannot distinguish a uniformly mediocre service from a fast one with a slow slice.
"What does rate() do over a 5-minute window?" Per-second average increase over that window, extrapolated to the window edges. At least two samples are required; a window of roughly four scrape intervals is a practical default that tolerates scrape alignment and a missed scrape.
"How would you alert on latency from a histogram?" On the ratio of requests inside the good bucket to total, which is an SLI, rather than on the quantile itself. It is cheaper and it is what an error budget consumes.
Common mistakes
Using a summary and then averaging quantiles across instances, which is silently wrong rather than visibly wrong.
Leaving default histogram buckets, so no boundary sits near your actual SLO and every quantile is a wide interpolation.
Putting rate() on a gauge, which produces a number that looks plausible and means nothing.
References