How does a metrics backend estimate a p99 latency from histogram bucket counts, and how large is the error?
answer
- Counts survive, individual measurements do not
- Find the bucket holding the target rank
- A straight line drawn inside one bucket
- Uncertainty equals the width of that bucket
basics
~20 sA backend stores only per-bucket counts, never individual measurements. It locates the bucket holding the 99th-percentile rank and interpolates linearly across it, so the reported value can be wrong by the entire width of that bucket.
solid answer
~50 sA latency histogram fixes a set of **bucket boundaries** up front and counts how many observations fall into each. The individual durations are discarded. To estimate a p99 the backend computes the target rank as `0.99 x total count`, walks the boundaries until the accumulated count first reaches that rank, and then draws a straight line across that one bucket: if the rank sits 27% of the way through the bucket bounded by 100 ms and 250 ms, the reported value is about 141 ms. That interpolation assumes the observations are spread evenly inside the bucket, which is exactly what the histogram never recorded. So the only defensible statement is that the true p99 is above 100 ms and at most 250 ms. Extra traffic, more frequent collection and more decimal places change nothing; only narrower boundaries around the region of interest do.
code
pseudocode · 15 lines# one window: cumulative count at or below each boundary (ms)
boundaries = [ 5, 10, 25, 50, 100, 250, 1000, INF]
cumulative = [40118, 214906, 431552, 481337, 496802, 501455, 502987, 503118]
total = 503118
rank = 0.99 * total # 498086.82
# first boundary whose cumulative count reaches the rank -> 250
lower, upper = 100, 250
count_below_lower = 496802
count_in_bucket = 501455 - 496802 # 4653
fraction = (rank - count_below_lower) / count_in_bucket # 0.2761
p99 = lower + fraction * (upper - lower) # ~141.4 ms
# truth: somewhere in (100, 250]. The digits are manufactured.go deeper
Be ready to say that a histogram keeps counts per bucket rather than individual timings, and that a reported p99 is therefore an estimate rather than a real request's duration.
Expect to walk the estimate through step by step: total count, target rank, which bucket the rank falls into, and the straight-line interpolation across that one bucket.
Show that the uncertainty is the bucket width, and that when a number must survive an argument you reach for an exact ratio of counts at a boundary instead of an interpolated quantile.
Own the position that reported quantile precision is a property of the bucket grid the organisation chose, and decide where that grid has to be consistent across teams for numbers to be comparable at all.
## What a histogram keeps, and what it throws away A latency histogram is not a list of measurements. When the instrument is created a set of **bucket boundaries** is fixed — say 5 ms, 10 ms, 25 ms, 50 ms, 100 ms, 250 ms and 1 s — and every observation increments exactly one counter: the counter for the bucket its value falls into. Some formats store a **cumulative** count against each boundary (how many observations were at or below it); others store the count **inside** each bucket. The two carry the same information, a running sum in one direction and a difference in the other. Alongside the buckets the instrument keeps a **total count** and usually a **running sum** of every value observed. That is the entire record. The individual durations are gone the moment they are counted. Two windows in which the same 4,653 requests landed between 100 ms and 250 ms — one where they all took 104 ms, one where they all took 249 ms — are identical on the wire. ## Locating the rank Estimating a quantile from that record is a two-step procedure. 1. **Compute the target rank.** For the 99th percentile of `N` observations the rank is `0.99 x N`. 2. **Walk the boundaries** in ascending order, accumulating counts, until the running total first reaches that rank. The bucket you stopped in is the bucket that contains the value you want. At that point you know something exact and something unknowable. Exact: the answer is greater than the previous boundary and no greater than this one. Unknowable: where inside that interval it sits, because the histogram never recorded it. ## The interpolation, worked Take the billing API of a district-heating system over one window. It served 503,118 requests, with 496,802 completing at or below 100 ms and 501,455 at or below 250 ms. - Target rank: `0.99 x 503,118 = 498,086.82`. - 496,802 is below that and 501,455 is above it, so the p99 falls in the 100 ms–250 ms bucket. - That bucket holds `501,455 - 496,802 = 4,653` observations, and the rank sits `498,086.82 - 496,802 = 1,284.82` of them in — about 27.6% of the way through. - **Linear interpolation** assumes those 4,653 observations are spread evenly across the interval, so the estimate is `100 + 0.276 x (250 - 100)`, roughly **141 ms**. 141 ms looks like a measurement. It is the midpoint of an assumption. ## Why the error is exactly the bucket width The interpolation assumes a uniform distribution inside the bucket — precisely the shape the histogram discarded. Real latency inside a tail bucket is rarely uniform: it is often clustered near the lower edge, which biases the interpolated value high, or piled up against a timeout, which biases it low. Neither can be detected from counts alone. So the honest statement about that 141 ms is: **the true p99 is above 100 ms and at most 250 ms.** Nothing in the data narrows it further. More traffic does not help — it stabilises the *rank* but leaves the resolution untouched. Collecting more often does not help. Only narrower boundaries where the quantile lands do. | Question you ask of the histogram | How good the answer is | | --- | --- | | How many requests were at or below 250 ms? | Exact — it is a counted value | | What fraction finished under 250 ms? | Exact — a ratio of two counted values | | What is the p99? | Estimated — interpolated inside one bucket | | What was the slowest request? | Unanswerable — the top bucket is open-ended | | What is the mean? | Exact — the running sum divided by the count | ## The practical consequence The table suggests the move experienced engineers make: **when a number has to be defensible, ask a question the histogram can answer exactly.** Place a boundary exactly at the threshold you care about, and "what fraction of requests finished under the threshold" becomes a ratio of two counters with no estimation in it at all — a far better basis for an objective than an interpolated quantile whose uncertainty is the width of a bucket somebody chose months ago. Use the interpolated quantile for what it is good at — trend, shape, and comparison between two similar windows — and be explicit that its resolution is bounded by the bucket grid. When someone on a three-person on-call rotation is arguing at 02:00 about whether the p99 moved from 141 ms to 158 ms, the useful answer is usually that both numbers came out of the same bucket and the histogram cannot tell them apart.
- Two backends read the same bucket counts and report different p99s. What could explain that?The estimate is a convention, not a measurement, so implementations differ. They may interpolate differently inside the bucket, treat the region below the lowest boundary differently, disagree about what to report when the rank lands in the open-ended top bucket, or cover a different query window. None of them is reading a stored p99; each is deriving one from the same counts under slightly different assumptions.
- Does interpolating inside the bucket make the reported p99 more accurate than simply reporting the bucket's upper boundary?No. Interpolation adds precision, not accuracy. Reporting the upper boundary is a guaranteed upper bound on the true value; the interpolated number is a guess at where inside the interval the value sits, based on a uniformity assumption the data never supported. Interpolation is useful because it moves smoothly as traffic shifts, which makes trends readable, but a change smaller than the bucket width is not evidence of anything.
It is like being told that 4,653 people are between 100 cm and 250 cm tall and then guessing one person's height by assuming everyone is evenly spaced across that range.
saying these in an interview costs you the question
- Claims the backend keeps every duration and sorts them
- Treats an interpolated p99 as accurate to the millisecond
- Thinks more traffic improves the resolution of the estimate
- Believes a p99 is read directly off one bucket's count
- Says the maximum latency can be read from the histogram