System designCore12 min

Latency

How long one request takes, why the slowest 1% is the number to watch, and where the milliseconds actually go: distance, round trips, disks, locks, pauses and queues.

1 · The idea

Every request has its own clock

A shopper in London presses Buy. The shop's servers are in Virginia. About 80 milliseconds later the confirmation appears. That 80 ms is the request's latency as most engineers use the word: the time from sending a request until its answer arrives. Strictly, that end-to-end figure is the response time, and some authors keep latency for the part of it a request spends waiting to be handled. This article uses the everyday meaning, and says wait time when it means the strict one.

Inside the response time there are two very different things. Service time is time spent actually working on the request: running code, reading rows. Wait time is everything else: bytes in flight on the network, the request sitting in a queue behind others. The rule from queueing theory is response time = service time + wait time, and under load it is the wait that grows, not the work.

ShopperLondonAPI service4 ms of workDatabase6 ms of work70 ms round trip0.5 ms round tripShopperLondonAPI service4 ms of workDatabase6 ms of work70 ms round trip0.5 ms round trip

One request's response time is the sum along its path: 70 + 4 + 0.5 + 6 ≈ 80 ms. Only 10 ms of it is work. 70 ms is the Atlantic.

Latency is measured per request. A service that handles 1,000 requests a second produces 1,000 different numbers every second, and they are not alike: most requests take the short path, a few hit a slow disk, a lock or a pause. There is no such thing as the latency of a service. There is a distribution, and the next section is about how to read it.

2 · Measuring it

The average describes a request nobody sent

Take a minute's worth of response times and sort them. The p50, or median, is the value in the middle: half the requests were faster. The p95 is the value 95% of requests beat; the p99, 99%. Everything to the right of the p99 is the tail. Now compare those with the mean:

p50
20 ms
mean
33 ms
p90
72 ms
p95
104 ms
p99
205 ms
p99.9
440 ms

Response times modeled as log-normal with median 20 ms and σ = 1: mean = 20 × e^0.5 ≈ 33 ms, p99 = 20 × e^2.33 ≈ 205 ms. The mean is 65% above the median and below the p90: it is the time of no typical request and no slow one.

The mean is dragged upward by a few very slow requests, so it overstates the typical case and understates the bad one. Percentiles describe real requests, which is why targets are written as percentiles: p99 under 200 ms, not average under 50 ms. And the p99 is not an outlier you can ignore: at 1,000 requests a second it is ten slow requests every second, and a busy user who makes hundreds of requests a day meets it daily.

Two traps catch almost everyone the first time they measure latency. Both come with numbers you can check.

Trap one: averaging percentiles. Two servers sit behind your load balancer. Server B has a failing disk, so the balancer has all but stopped sending it traffic:

Server

Requests this minute

p99

A

9,990

30 ms

B

10

900 ms

A dashboard that averages the two shows (30 + 900) ÷ 2 = 465 ms, and someone gets paged. The real p99 of the 10,000 requests is about 30 ms. Why? The slowest 1% is 100 requests. B supplied only 10 of them; the other 90 are A's slowest, which sit right at A's own p99. The average gave ten requests the same vote as 9,990.

The same thing happens over time: the p99 of an hour is not the mean of sixty one-minute p99s. Keep the raw values, or histograms you can add together, and compute the percentile from the merged data.

Trap two: the load test that waits politely. Your test sends a request, waits for the answer, then sends the next: 100 a second, 10 ms each. Halfway through, the server freezes for 10 seconds. The tester is stuck waiting on one request, so it logs one slow request and carries on. Real users do not wait their turn. They keep arriving, 100 a second, all through the freeze.

Over a 100-second run

What the test recorded

What users would have felt

Requests

9,001

10,000

Requests hit by the freeze

1

1,000

p99

10 ms

About 9 s

This is coordinated omission: the tester and the server stall together, so the test skips exactly the requests that would have suffered. The report says p99 = 10 ms for a system that made a thousand people wait. The fix is to send on a fixed schedule whatever the server does, and to time each request from the moment it should have been sent.

3 · Where the time goes

Light is slow, and every handshake costs a round trip

Why does crossing the Atlantic cost about 70 ms? Because light is slow. In glass fiber it manages about two-thirds of its vacuum speed: 299,792 km/s ÷ 1.468, the refractive index of standard fiber, is roughly 204,000 km/s. One rule falls out of that, and it is worth remembering: every 100 km of distance costs about 1 ms of round trip.

New York to London is about 5,570 km in a straight line, so physics alone charges 55 ms there and back. Measured round trips on that route sat at about 70 ms throughout 2025. The other 15 ms is the cable, which does not run in a straight line, and the routers along it. No code change removes any of this. The only way to pay less is to be closer.

A fresh HTTPS connection over a 70 ms round trip: one round trip for TCP's three-way handshake, one for the TLS 1.3 handshake, one for the request: 3 × 70 = 210 ms before the first byte of the page. TLS 1.2 needed two round trips of its own: 280 ms. On a connection kept open from last time: 70 ms.

Distance and handshakes are the network's share. The rest of the time goes to things inside the machines, and each has its own scale:

Source of delay

What happens

Typical cost

Disk instead of memory

A read that misses every cache goes to storage

Memory ≈ 100 ns; NAND SSD tens of µs; spinning disk ≈ 10 ms

Serialization

Objects become bytes and back; CPU time grows with payload size

A megabyte of JSON: milliseconds with an ordinary parser

Waiting for a lock

Your thread waits for whoever holds the lock to finish

Uncontended: tens of ns. Contended: as long as the holder takes

Garbage-collection pause

The runtime stops every thread to reclaim memory

Under 1 ms with modern collectors, up to a few hundred ms with older ones

Cold start

A new instance loads code and opens connections before serving

Serverless functions: under 100 ms to over 1 s, on under 1% of invocations

Queueing

The request waits behind others for a busy CPU, pool or disk

Grows steeply as the resource fills: see Throughput, utilization & queueing

4 · The numbers

One disk read is a hundred thousand memory reads

Every engineer should carry a rough price list in their head: what a memory read, a disk read and a network hop each cost. The digits shift with every hardware generation, and the lists copied around the internet often still show figures from 2010. So learn the order of magnitude of each rung, not the digits:

Operation

Roughly

How it has moved

L1 cache reference

1 ns

About the same since 2010

Uncontended mutex lock and unlock

20 ns

About the same since 2012

Main memory reference

100 ns

Unchanged since 2010

Compress 1 KB with a fast compressor

2–3 µs

Slightly faster than in 2012

Random 4 KB read from an SSD

30–50 µs

Was about 150 µs in 2012; drives got faster

Round trip inside one data center

0.1–0.5 ms

Was 0.5 ms in 2010; about 0.25 ms within one cloud zone

Random read from a spinning disk

10 ms

≈ 10 ms: a few ms of seek plus 4.2 ms of half a rotation at 7,200 rpm

Round trip New York to London

70 ms

Measured, 2025

Round trip across the Pacific

110 ms

Measured, 2025

Round trip California to Netherlands

150 ms

A typical measurement; light in fiber alone would take about 90 ms

Read it as a ladder. Memory to SSD is about 300×; SSD to spinning disk about 300× again; disk to an ocean crossing about 7×. So one disk read costs about 10 ms ÷ 100 ns = 100,000 memory reads, and one transatlantic round trip about 700,000. That is why a design that adds one more round trip per item, or one more disk read per request, is never a small change.

5 · The catch

Fan-out makes the tail everyone's problem

The checkout page does not make one backend call. It asks the cart service, the pricing service, the stock service, the recommendation service, and each of those asks others: fan-out. The page is ready when the slowest answer arrives. If each backend is slow on 1% of calls, and the slow calls are independent, the chance that a page with n calls hits at least one slow call is 1 − 0.99ⁿ.

020406020406080100Backend calls per pagePages that hit a slow call (%)

With 100 backends, 1 − 0.99¹⁰⁰ ≈ 63% of page loads wait on somebody's p99. This is known as the tail at scale, or tail latency amplification.

Rarer tails do not escape either: a p99.9 event, 1 in 1,000, hits 1 − 0.999¹⁰⁰⁰ ≈ 63% of requests that fan out to 1,000 servers. At scale the tail is not caused by broken machines but by ordinary ones: a background daemon, a shared network switch, a garbage-collection pause, a queue that happened to be long. You cannot remove those, so wide systems are designed to be tail-tolerant instead: they finish quickly even when a few parts are slow.

6 · The fix

Cut distance, round trips and waiting, in that order

Before optimizing code, find the biggest term in the sum. In the checkout example that is 70 ms of distance and 140 ms of handshakes, against 10 ms of work. Every move below removes a term:

Move

What it removes

In the example

Cache the answer

The trip to the database, or the whole trip to the origin (see Caching strategies)

A hit skips 6 ms of database work and a round trip

Move closer

Distance: an edge or a nearby region

Within 1,000 km the round trip is under 10 ms instead of 70

Reuse connections

The TCP and TLS handshakes, with keep-alive and connection pools

210 ms to first byte becomes 70

Batch

Round trips: one request for twenty items instead of twenty requests

20 × 70 ms becomes 1 × 70 ms

Do things in parallel

The sum: independent calls cost the slowest, not the total

Four 20 ms calls take 20 ms, not 80

Hedge

The tail: a second copy of a slow request

See below

Parallel calls make the page wait for the slowest answer, the tail from the chart above. The fix for that is the hedged request:

  • Send the request to one replica.
  • If no answer has arrived by the time the p95 latency has passed, send a copy to a second replica.
  • Take whichever answers first and cancel the other.

Only the slowest 5% of requests ever get a second copy, so the extra load is about 5%, while the tail shrinks. Hedge only calls that are safe to run twice, such as reads, and only when the replicas exist.

Check yourself

5 questions

1. A service reports a mean response time of 33 ms and a median of 20 ms. What does the mean tell you about a typical request?
2. Server A's p99 is 100 ms and server B's is 300 ms, with equal traffic. What is the p99 across both?
3. A page calls 50 backends in parallel and each one is slow on 1% of calls, independently. Roughly what share of page loads hit at least one slow call?
4. Users in Sydney see 900 ms page loads from servers in Virginia. Profiling shows each request needs 5 ms of server work. Which single change helps most?
5. When does a hedged request send its second copy?

Takeaways

Remember this

  • Latency is one request's time; response time = service time + wait time, and under load it is the wait that grows.
  • Read the distribution with percentiles (p50, p99), not the mean; never average percentiles, and measure from when each request should have been sent.
  • The speed of light in fiber costs about 1 ms per 100 km of round trip, and a fresh HTTPS connection spends two round trips shaking hands before the request.
  • Know the ladder: memory ≈ 100 ns, SSD ≈ tens of µs, disk ≈ 10 ms, data center round trip ≈ 0.5 ms, ocean crossing ≈ 70 to 110 ms.
  • Fan-out turns each backend's rare slow call into a common slow page: 1 − 0.99¹⁰⁰ ≈ 63%. Cut distance, round trips and waiting first; hedge the tail.