When you call an API, you are not doing one thing. You are starting a relay race with eight distinct stages, each with its own protocol, its own failure modes, and its own cost. Most developers can name the stages. Almost nobody can tell you what each one actually does — or why that byte of payload took eleven milliseconds to get there.
This article traces one request through the entire stack. Everything described here is
verifiable with curl, tcpdump, and a database you control. The input, the runtime, and the
output — all of it.
trace / request.md
readyThe browser parses the URL and builds an HTTP request. Before a single byte can leave, it must resolve the hostname to an IP address.
Stage 1 — The browser parses the URL
Before anything leaves the machine, the URL itself is decomposed. https://api.example.com/v1/orders?limit=10 becomes five separate pieces: the scheme, the host, the port, the path, and the query string.
The scheme matters more than people give it credit for. https tells the browser not just "encrypt this" but which port and which TLS version to negotiate. It also decides whether the request is eligible for connection reuse through the browser's HTTP cache and HSTS policy.
Nothing has touched the network yet. This stage costs microseconds and almost always fails: the URL is syntactically invalid, or the host is empty. Everything else — everything below — can fail in a dozen ways.
Stage 2 — DNS turns a name into an address
The browser needs an IP address, and the only machine it knows by default is its configured DNS resolver. That resolver is almost never the authoritative server for your domain — it's a recursive resolver, usually operated by your ISP or a public resolver like 1.1.1.1.
The recursion looks like this:
you -> recursive resolver -> root server -> TLD server (.com) -> authoritative server (api.example.org)Each response travels down the hierarchy, and every level caches aggressively:
| Level | Typical TTL | Typical latency |
|---|---|---|
| OS resolver cache | minutes | 0 µs |
| Recursive resolver cache | TTL (30 s – 24 h) | 0–1 ms |
| Full recursive walk | TTL | 20–150 ms |
The addition of DNSSEC means responses are cryptographically signed — and if a response doesn't validate, the resolver returns SERVFAIL rather than an address. That's the failure most people misdiagnose as "the DNS is broken" when in fact it's "the chain of trust is".
The cost: one address lookup, most often served from a cache in sub-millisecond time. When it misses, it costs a full round trip plus the walk time — quiet, invisible, and occasionally the entire reason a request "feels slow".
Stage 3 — TCP opens a connection
Now the browser has an IP address, and it needs a connection. TCP's three-way handshake is the most famous exchange in networking for one reason: it establishes state on both ends before a single byte of payload moves.
client server
| SYN | 1. propose: seq = x
|--------------->|
| SYN-ACK | 2. accept + propose: ack = x+1, seq = y
|<---------------|
| ACK | 3. confirm: seq = x+1, ack = y+1
|--------------->|Why does the handshake exist at all? Because TCP is a reliable, ordered, byte-stream protocol on top of an unreliable network. Both sides must agree on sequence numbers so that packets can be reordered, duplicated, or lost without corrupting the stream. Without the handshake, there's no way to tell a late duplicate from a new transmission.
The handshake isn't the only cost. After the handshake, TCP's congestion control starts with a small send window — a "slow start" — because the new connection knows nothing about the path. The first few round trips carry only a few packets until the window opens up. On a cold connection, the first request effectively pays for the learning.
The cost: one RTT minimum for the handshake, plus the ramp-up of slow start. On a 50 ms RTT, that's 50–150 ms before your bytes even start transmitting.
Stage 4 — TLS encrypts the channel
TCP gives you a pipe. TLS gives you a private pipe — and it does it in what is now, in TLS 1.3, exactly one round trip:
client server
| ClientHello (cipher suites, key share) |
|--------------------------------------------->|
| ServerHello + EncryptedExtensions + |
| Certificate + Finished |
|<---------------------------------------------|
| Finished |
|--------------------------------------------->|
[ application data begins immediately ]Two details are worth internalizing.
First, the certificate is the authority. The server proves who it is by presenting a certificate signed by a chain that ends at a root certificate the client already trusts. The handshake does not encrypt against the server's IP or DNS name — it encrypts against the key the certificate proves belongs to that name. That is why TLS does not protect you from a DNS poisoning attack on its own: the attacker can steer your packets, but they can't present a valid certificate.
Second, everything after the handshake uses symmetric encryption with a per-connection session key derived in the handshake. Asymmetric crypto (RSA, ECDHE) is expensive; symmetric crypto (AES-GCM, ChaCha20) is cheap. The whole design of TLS is: use the expensive stuff exactly once, then switch to the cheap stuff.
The cost: one RTT in TLS 1.3, two in TLS 1.2. Combined with TCP, a brand-new connection costs 2–3 RTTs — often more than the actual server work. This is why connection reuse matters.
Stage 5 — HTTP writes the request
Now, finally, bytes. The browser writes a request that looks like this:
GET /v1/orders?limit=10 HTTP/2
Host: api.example.org
authorization: Bearer eyJhbGciOiJIUzI1NiIs...
accept: application/jsonUnder HTTP/1.1, each of these lines goes over the wire in clear-ish form (well, encrypted by TLS, but readable inside the tunnel), one request per connection at a time. Under HTTP/2, the request is framed: split into HEADERS and DATA frames, multiplexed over a single connection alongside other concurrent requests.
The interesting part is what HTTP doesn't define. It doesn't define routing, caching semantics beyond the headers, or authentication. It defines a uniform interface: a method, a target, and a status code. That uniformity is why load balancers can terminate HTTP, why caches can interpose, and why an entire observability industry can infer latency from HTTP logs alone.
Stage 6 — The load balancer picks a backend
Your request has now crossed the internet and arrived at the front door of the infrastructure. If that door is a load balancer, it is about to make a decision that is genuinely hard to make well:
Which of the servers behind it should handle this request?
The options, in increasing sophistication:
- Round-robin — fair in the average case, brutal when a backend is slow. A slow server will keep accepting requests and keep being slow.
- Least connections — better for requests with variable duration, but only as good as the health signal.
- Consistent hashing — routes a given key (a user ID, a tenant) to the same backend every time. This is how sticky caches survive fleet changes.
Every option has to be combined with health checks, because routing to a dead server is the one failure that makes all the cleverness irrelevant. Health checks are themselves requests — they occupy the same port, the same connection pool, and the same queue as real traffic.
Stage 7 — The server does the work
Now the request is inside your application process. The path from here is entirely your design:
- Parse and validate the request (path, headers, body).
- Authorize it — authentication happened earlier, but authorization is server-side policy.
- Load data — usually by asking the database, sometimes answered by cache.
- Render or serialize the response.
- Return control to the framework, which writes headers and bytes back.
The detail that separates people who understand servers from people who operate them: the money is in the queues. The average request spends far more time waiting — for a thread, for a connection from the pool, for a lock, for the event loop — than doing work. That's why a server can be "idle" at 10% CPU and still return 1102 Timeout to customers.
Stage 8 — The database answers
The database is not one thing, either. The moment your SQL arrives:
- The planner turns the query into a plan — choosing between an index scan, a sequential scan, a hash join — based on statistics it assumes are roughly right.
- The executor runs the plan, and for every page it needs, asks the buffer pool. If the page isn't cached in memory, it must come from disk — and that's exactly one iops away, 10–100× slower than memory.
- Every write goes through the write-ahead log before it touches the data files, so that either the transaction commits or the system recovers, never in between.
- Concurrency control — locks, snapshots, indexes in transit — decides who sees what version of the row.
A "simple" SELECT can touch all of these systems. The database is the one stage in this relay where the work is genuinely computed, and it's the one stage most teams black-box.
The way back
The response doesn't retrace the path byte-for-byte — the database result goes back over the same TCP connection, the server frames an HTTP/2 DATA frame, the load balancer forwards it, and the browser's TLS layer decrypts it with the session keys established in stage 4. But the structure of the trip is symmetric, which is why the latency model is symmetric too: the things you paid for on the way in — handshakes, connection setup, waits — are the things you pay for on the way out.
What did the whole trip cost?
A realistic budget for a cold, uncached, small API call across a normal internet path:
DNS 0–100 ms (cached: ≈0)
TCP handshake 1 RTT (≈20–60 ms)
TLS handshake 1 RTT (≈20–60 ms)
Request travel ½ RTT (≈10–30 ms)
Server work 1–50 ms (the interesting part)
Database 1–20 ms (if cache misses, + 5–20 ms disk)
Response travel ½ RTT (≈10–30 ms)
--------------------------------------------
total 40–350 msThe same request with warm caches and a reused connection:
DNS ≈ 0 · TCP ≈ 0 · TLS ≈ 0 · server 2–10 ms · total ≈ 10–40 msThat gap — up to an order of magnitude — is not "the network being slow". It's the difference between understanding the runtime and hoping the runtime cooperates. Trace it, and both the systems and the tradeoffs become visible. That is the point of this platform.