Calculator

Latency Percentile Calculator (p50, p95, p99)

Updated October 27, 2026•7 min read•Latency•Percentiles

Written by

Reviewed by the LoadTester editorial team. Review process: see the editorial policy.

Published
2026-10-27
Last reviewed
2026-10-27
Author
Kristian Razum
Latency Percentile Calculator (p50, p95, p99): LoadTester illustration

To calculate p95 latency, sort your response times and take the value below which 95% of them fall: with the nearest-rank method that is the value at position ⌈0.95 × n⌉, so the 57th of 60 sorted samples or the 950th of 1,000. Paste your own latencies below to get p50, p90, p95, p99, p99.9, mean and a histogram, computed in your browser with both nearest-rank and linear interpolation so you can see where tools disagree.

Count–
Min–
Mean–
Max–
PercentileNearest-rank (ms)Interpolated (ms)

How the percentiles are calculated

The calculator sorts your samples ascending (x[1] ≤ x[2] ≤ … ≤ x[n]) and reports two methods.

Nearest-rank (the primary column) returns an actual observed value:

rank = ceil(p / 100 × n)        (minimum 1)
P(p) = x[rank]

It never invents a latency nobody experienced, and its meaning is exact: at least p% of requests were at or below this value. That makes it a good fit for SLO checks and pass/fail thresholds.

Linear interpolation (the second column) is the default in NumPy (numpy.percentile) and Excel's PERCENTILE.INC, sometimes called the R-7 or "inclusive" method:

h    = (n − 1) × p / 100            (0-based position)
P(p) = x[floor(h)] + (h − floor(h)) × (x[floor(h)+1] − x[floor(h)])

It returns a value between two neighboring samples. With large sample counts the two methods converge; with small ones, and especially in the tail, they can differ a lot.

Worked example with the sample data

The pre-filled data is an illustrative set of 60 response times with a typical right-skewed shape: most requests between 90 and 200 ms, a tail stretching to 1,310 ms.

StatisticNearest-rankInterpolatedHow it was found
p50124 ms125 msrank ⌈30⌉ = 30th value; interpolated halfway between 30th and 31st
p90236 ms239.2 msrank ⌈54⌉ = 54th value
p95389 ms393.85 msrank ⌈57⌉ = 57th value
p991,310 ms974.9 msrank ⌈59.4⌉ = 60th value, the max
Mean175.2 mssum ÷ 60

Two lessons from this one small sample. First, the mean (175 ms) is 40% above the median (124 ms), because a few slow requests drag it up; this is why load test reports lead with percentiles. Second, the p99 values differ by 335 ms between methods, and the nearest-rank p99 is just the maximum. With 60 samples there is not enough data to say anything about the 99th percentile; both numbers are mostly noise.

How many samples each percentile needs

A percentile is only as good as the number of samples beyond it. If p99 rests on one or two requests, the next run will move it substantially.

PercentileSamples above it per 1,000Minimum n to differ from maxComfortable n
p505002100+
p9550201,000+
p99101005,000+
p99.911,00050,000+

The "comfortable" column is a rule of thumb (roughly 50 or more samples above the percentile), not a statistical guarantee. The practical upshot: a 1-minute test at 50 RPS gives 3,000 samples, enough for a solid p95 and a rough p99. A 5-minute test at 500 RPS gives 150,000 samples, enough for p99.9. More on choosing between them in p95 vs p99 latency.

Why different tools report different p95 values

If k6, JMeter, your APM and Prometheus all show slightly different p95s for the same test, that is usually expected:

  • Method. Nearest-rank, linear interpolation and other definitions differ most in the tail and at small n, as the table above shows.
  • Histogram buckets. Prometheus histogram_quantile() does not see raw samples. It finds the bucket containing the percentile and interpolates linearly inside it, so with buckets at 250 ms and 500 ms, any p95 inside that range is an estimate. Choose bucket boundaries around your SLO threshold.
  • What is timed. Client-side tools measure from request send to last byte received, including network. Server-side metrics usually start after the request is parsed. Neither includes time a request spent waiting in a load generator that was blocked, which is coordinated omission.
  • Aggregation. Percentiles cannot be averaged. The mean of per-minute p95s is not the p95 of the test. Merge raw samples or mergeable histograms instead.

Using percentiles in a load test

Percentiles turn a load test into a pass/fail check. Pick the percentile that matches your SLO and set a threshold on it:

// k6 example
export const options = {
  thresholds: {
    http_req_duration: ['p(95)<400', 'p(99)<1000'],
    http_req_failed:   ['rate<0.01'],
  },
};

In LoadTester you set the same kind of threshold (for example p95 < 400 ms) on a test, watch p50/p95/p99 on the live chart, and can have the run auto-stop when the threshold is breached. Whatever tool you use, check three things when reading the result:

  1. Percentiles over time, not just the summary. A p95 that is flat for nine minutes and doubles in the tenth is a capacity limit, not a fluke. See why latency increases under load.
  2. The gap between p50 and p99. A wide, growing gap points to queueing, lock contention or GC pauses; a uniform shift of all percentiles points to a slower dependency.
  3. Sample count. Make sure the test produced enough requests for the percentile you are gating on, per the table above.

For the full workflow of reading a report, see how to read load test results. If your SLO is phrased as "99% of requests under 300 ms", the error budget calculator shows how much slack that leaves per month.

Getting raw latencies to paste

Most sources can export raw durations in milliseconds:

# nginx: add $request_time (seconds) as the last field of log_format, then:
awk '{print $NF * 1000}' access.log | tail -n 5000

# curl, 50 sequential requests (not a load test, just a baseline)
for i in $(seq 50); do curl -o /dev/null -s -w '%{time_total}\n' https://example.com/; done \
  | awk '{print $1 * 1000}'

# k6: write raw results, then extract http_req_duration points
k6 run --out json=out.json script.js
grep '"metric":"http_req_duration"' out.json | grep '"type":"Point"' \
  | sed 's/.*"value":\([0-9.]*\).*/\1/'

The calculator handles tens of thousands of values comfortably; everything runs in your browser and nothing is uploaded.

FAQ

How do I calculate p95 latency by hand?

Sort the response times from fastest to slowest and multiply the count by 0.95. With the nearest-rank method, round that up to the next whole number and take the value at that position; for 200 samples that is the 190th value. Spreadsheets and NumPy use linear interpolation by default, which can give a slightly different number.

Why is my p99 the same as the max?

With fewer than 100 samples, the nearest-rank p99 lands on the last sorted value, which is the maximum. You need at least 100 samples for p99 to differ from the max and at least 1,000 for p99.9. Collect more data before drawing conclusions about the tail.

Can I average p95 values from several servers or time windows?

No. The average of percentiles is not the percentile of the combined data and can be badly wrong when the windows have different load. Merge the raw samples or a mergeable histogram (such as HDR Histogram) and compute the percentile from the merged data.

Why is the mean so much higher than the median?

Latency distributions are right-skewed: most requests are fast and a few are very slow. A handful of slow outliers pull the mean up while the median stays near the typical request. That is why load test reports use percentiles instead of averages.

Which percentile should I put in my load test threshold?

p95 is the usual choice for a pass/fail threshold because it is stable with a few thousand samples and reflects what a meaningful share of users experience. Watch p99 as well, since it is where queueing and garbage collection pauses show up first, but make sure the test has enough requests for it to be meaningful.

Run this as a real load testFree plan: 10 VUs, 50 RPS, live p95/p99 and error charts, no credit card. Starter $19/mo (250 VUs) or Pro $49/mo (2,000 VUs) with a 7-day free trial when you need more.
Start freeCompare plans