Engineering Atlas › System Design Fundamentals

Latency, throughput and bandwidth compared

14 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

Three different questions

On a highway, bandwidth is the number of lanes, latency is how long one car takes from on-ramp to exit, and throughput is how many cars pass the exit per hour. Adding lanes raises the throughput ceiling but makes no single car faster.

Little's law

For any stable system, the average number of items inside it equals the arrival rate times the average time each spends inside: L = λ x W. A service handling 2,000 requests per second at 50 ms average latency has about 100 requests in flight at any moment. This is why thread pools, connection pools and TCP windows all have to be sized from both numbers. If the pool holds only 50, the service cannot exceed 1,000 requests per second at that latency, no matter how idle the CPU looks.

Utilisation and queueing

A server that takes 10 ms per request can, in theory, do 100 per second. In practice, requests arrive in bursts, and when one arrives while the server is busy, it waits. For a simple single-server queue, average time in the system is service_time / (1 - utilisation). At 50% utilisation, 10 ms becomes 20 ms. At 90%, it becomes 100 ms. At 99%, a full second. Throughput rose by under a factor of two while latency rose fifty-fold. This curve is why capacity plans target 60 to 70% utilisation for latency-sensitive services.

Batching trades one for the other

Sending one message per network round trip wastes the fixed cost of each trip. Grouping 100 messages into one write raises throughput dramatically but makes the first message wait for the batch to fill. Kafka producers expose this directly with linger.ms and batch.size; databases do it with group commit; GPUs do it with inference batches. Batching does not always cost latency, though. Kafka 4.0 raised the default linger.ms from 0 to 5 ms because, under load, larger batches mean fewer requests queueing at the broker, so producer latency often stays the same or drops (Kafka docs). The right setting depends on whether the consumer cares about the time of each item or the total volume.

Tails dominate at scale

If a page fans out to 100 backends and each has a 1% chance of a slow response, the page is slow 1 - 0.99^100 ≈ 63% of the time (Dean and Barroso, 2013). The average backend latency is irrelevant; the p99 of each backend becomes the median of the page. The same paper shows the fix can be cheap: in a Bigtable test, sending a backup request after 10 ms cut the 99.9th-percentile latency from 1,800 ms to 74 ms for only 2% more requests. This is the fan-out shape. The same maths applies to an AI agent that calls ten tools in parallel and to a checkout page waiting on pricing, inventory and fraud checks: the slowest dependency sets the experience.

Down the stack

Layer 1The contract

Performance contracts are stated as a latency percentile at a throughput: "p99 under 200 ms at 5,000 requests per second, with error rate under 0.1%." A latency number without a load level is meaningless, because latency depends on load. A bandwidth number from a provider is a ceiling on raw bits, before protocol overhead (Ethernet, IP and TCP headers take about 3 to 5% at 1,500-byte packets) and before retransmissions.

Layer 2The mechanism

TCP throughput on one connection is bounded by window / RTT. The receiver advertises a window (scaled by the window-scale option, up to 1 GiB (RFC 7323)), and the sender keeps a congestion window that starts small (10 segments, about 14.6 KB, as specified in RFC 6928 and used by Linux) and grows each round trip during slow start. To fill a link, the window must cover the bandwidth-delay product: 1 Gbps x 80 ms = 10 MB in flight. Linux caps this with net.ipv4.tcp_rmem and tcp_wmem. The receive maximum stayed at 6 MB from 2012 until Linux 6.16 raised it to 32 MB, because receivers on long paths were capped by it (kernel commit); the send maximum still defaults to at most 4 MB (kernel docs). Congestion control (CUBIC by default, now a Standards Track spec in RFC 9438, which replaced RFC 8312; or BBR) decides how fast to grow and how hard to back off on loss. Above TCP, HTTP/2 multiplexes many requests on one connection to avoid repeated handshakes.

Layer 3The machine

Light in fibre travels about 200,000 km per second, so every 1,000 km adds about 5 ms one way. New York to London (about 5,600 km) can never be faster than roughly 56 ms round trip (High Performance Browser Networking). Serialisation delay is the time to put bits on the wire: a 1,500-byte packet takes 12 µs at 1 Gbps and 1.2 µs at 10 Gbps. Inside a machine, an L1 cache hit costs about 1 ns, main memory about 100 ns, an NVMe read about 20 to 100 µs. The classic 500 µs for a round trip within a data centre is a 2000s figure: the fabric itself now takes a few microseconds, and the kernel, scheduler wake-ups and RPC stack add tens of microseconds or more on top (Barroso et al., 2017). NICs coalesce interrupts (ethtool -C rx-usecs) and the kernel merges packets with GRO, which raises throughput by doing less work per packet at the cost of a few microseconds of added latency per packet: the batching trade-off again, in hardware.

Why it's built this way

  1. Distance has a floor set by the speed of light, so latency between regions cannot be engineered away, only avoided with caches and replicas closer to users.
  2. Every operation has a fixed cost (a round trip, a syscall, a disk seek), so systems batch work to spread that cost, trading latency for throughput.
  3. A pipe is only full if enough data is in flight to cover its delay, so windows and pools are sized by Little's law and the bandwidth-delay product.
  4. Queues grow without bound as utilisation nears 100%, so systems keep headroom and shed load rather than run hot.

Diagram

sequenceDiagram
    participant C as Client
    participant S as Server (RTT 80 ms)
    C->>S: SYN
    S-->>C: SYN-ACK (80 ms gone)
    C->>S: Request + first 10 segments
    Note over C,S: Slow start: window doubles each RTT
    S-->>C: ACKs (160 ms)
    C->>S: 20 segments
    S-->>C: ACKs (240 ms)
    C->>S: 40 segments ... until window covers 10 MB in flight
    Note over C,S: Steady state: throughput = window / RTT, capped by bandwidth

Worked example

A team copies a 1 GB backup from a data centre in Virginia to one in Frankfurt. The link is 1 Gbps and the round trip is 80 ms. Their naive estimate is 8 seconds.

With an old 64 KB TCP window and no window scaling, one connection can only have 64 KB in flight per 80 ms, so throughput is 64 KB / 0.08 s = 800 KB/s, about 6.4 Mbps. The copy takes over 20 minutes on a link that is 99% idle. The link's bandwidth was never the limit; latency times window was.

The bandwidth-delay product is 1 Gbps x 0.08 s = 80 Mbit = 10 MB. Raising tcp_rmem and tcp_wmem maximums to 16 MB lets the window grow to fill the pipe. On Linux 6.16 or later the receiver may already allow 32 MB, but the sender's 4 MB default still caps the flow at about 50 MB/s, so check both ends. Slow start needs about 10 round trips (under a second) to reach 10 MB, and the copy then finishes in roughly 9 seconds. A tool that opens 8 parallel connections achieves the same with smaller windows, which is why aws s3 cp and most bulk-transfer tools use multipart, parallel uploads (the AWS CLI defaults to 8 MB parts and 10 concurrent requests (AWS CLI docs)).

The same reasoning applies to the API in front of that system: at 3,000 requests per second and 40 ms average latency, Little's law says about 120 requests are in flight. With a database pool of 64, it plateaued near 1,600 requests per second while CPU sat at 35%, because requests queued for a connection.

In the wild

Trade-offs

LeverImprovesCostsChoose when
BatchingThroughput, cost per itemLatency of each itemLogs, analytics, bulk writes
More concurrency (pools, connections)ThroughputMemory, contention, more load on dependenciesLatency is fine but throughput is capped by in-flight limits
Caching and replicas near usersLatencyStaleness, cost, invalidation workRead-heavy, geographically spread users
Running with headroomTail latencyIdle capacity, moneyUser-facing, latency-sensitive paths
Hedged requestsTail latencyExtra load (typically 2 to 5%)Fan-out reads against replicated data
More bandwidthCeiling on throughputMoneyMeasured utilisation is near the link limit

Pitfalls in production

Leadership lens

Check yourself

  1. A service handles 500 requests per second with 200 ms average latency. How many requests are in flight, and what happens if the worker pool has 50 slots?

    Show answer

    About 100 are in flight by Little's law. With 50 slots, throughput caps at 250 per second at that latency and the rest queue, so latency rises.

  2. Why can't a single TCP connection fill a 10 Gbps link across a 100 ms round trip with a 1 MB window?

    Show answer

    Throughput is window divided by RTT, so 1 MB / 0.1 s = 10 MB/s, about 80 Mbps. Filling the link needs a window of about 125 MB or many parallel connections.

  3. A page calls 50 backends in parallel, each slow 1% of the time. Roughly how often is the page slow?

    Show answer

    1 - 0.99^50 ≈ 39% of the time, so the backends' p99 becomes the page's typical experience.

  4. Why does batching raise throughput but hurt latency?

    Show answer

    It spreads a fixed per-operation cost over many items, but each item waits for the batch to fill or for the flush timer before it is sent.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →