Lesson 1 of 5
The Request Path
"The network is the computer." — John Gage, Sun Microsystems
In Volume 0 you learned the pieces in isolation: DNS turns a name into an address, TCP carries bytes reliably, HTTP frames a request and a response. That was the mechanic's view — each part on its own bench. System design asks a different, scarier question: what actually happens in the ~200 milliseconds between a user's click and the pixels changing on their screen — and why does that path have seven boxes in it instead of one? Every one of those boxes was added by an engineer who was losing sleep over latency, cost, or a 3am page. Understanding the request path is understanding the skeleton every large system hangs off. It is also, not by accident, the diagram interviewers ask you to draw first — because if you can trace a request end to end, you can reason about where anything scales, breaks, or slows down.
A user clicks a link. Between that click and the response, a request crosses continents and half a dozen machines. What are those machines, what does each one add, and where does the time actually go?
Core Concepts
You already know a single request-response cycle: client opens a connection, sends GET /timeline, server replies. At scale, that straight line grows hops — each one a specialised layer that exists to solve a problem the layer before it couldn't. Here is the whole path, top to bottom, for a modern web request:
Walk it hop by hop, and notice what each box buys you:
- DNS resolution turns
api.example.cominto an IP. The result is cached at many levels (browser, OS, resolver) with a TTL, so most requests skip the lookup entirely. Increasingly the IP you get back belongs to a CDN edge, not your origin — geography-aware DNS steers each user to their nearest point of presence. - CDN (edge network). For static, cacheable bytes — images, JS/CSS bundles, video segments — the request ends here, at a server physically near the user, never touching your infrastructure. This is the single biggest latency win available, and it also shields your origin from load. A dynamic, personalised request falls through to the origin.
- Load balancer. The front door to your system. It spreads incoming requests across a pool of identical app servers and, crucially, routes around any server that fails a health check. It is a reverse proxy (more on that below).
- API gateway. A policy layer: it authenticates the caller, enforces rate limits, terminates TLS, routes
/timelineto the timeline service and/mediato the media service, and stamps a request ID for tracing. In small systems the LB and gateway collapse into one box. - App server. Where your business logic runs. The critical property, discussed below, is that it holds no per-user state — any server can handle any request.
- Cache. An in-memory store (Redis, Memcached) checked before the database. A cache hit answers in well under a millisecond; a miss falls through to the DB and usually populates the cache on the way back.
- Database. The source of truth — durable, consistent, and by far the most expensive hop to touch. The entire architecture above it exists, in large part, to avoid hitting it more than necessary.
Where the latency actually goes
You cannot cut time you cannot see. Here is a realistic budget for a cold, cross-region request — the numbers are typical mid-2020s figures, not laws of physics, but the ratios are what matter:
| Hop | Typical cost | Why it costs that | How you cut it |
|---|---|---|---|
| DNS lookup | 0–100 ms | Recursive resolution; often cached to ~0 | TTL caching, DNS prefetch |
| TCP + TLS handshake | 1–3 RTTs (~50–150 ms cross-region) | Round trips to establish + encrypt | Keep-alive, TLS session resumption, TLS 1.3, HTTP/2/3 |
| Network RTT (each way) | ~1 ms same-city, ~150 ms intercontinental | Speed of light in fibre is finite | CDN / edge, co-locate services in one region |
| Load balancer + gateway | 1–5 ms | Extra hop + policy checks | Keep it lean; not the bottleneck |
| App server processing | 1–50 ms | Your code, serialization, fan-out | Profile, parallelise, precompute |
| Cache read (hit) | < 1 ms | In-memory, same datacenter | Cache the hot path |
| Database query | 5–100+ ms | Disk, locks, joins, index scans | Indexes, connection pooling, read replicas, caching |
Read the pattern: the expensive hops are the ones that cross a network or hit a disk. A same-datacenter cache read is ~1000× faster than an intercontinental round trip. That is why the great latency wins are all about distance and avoidance: move data close to the user (CDN), reuse connections instead of re-handshaking (keep-alive), answer from memory instead of disk (caching), and keep a warm pool of DB connections ready (pooling) instead of paying the handshake per query.
Think of ordering food. DNS is looking up the restaurant's address. The CDN is the branch on your street — if they have what you want, you never call head office. The load balancer is the host who seats you at whichever waiter is free. The app server is the waiter taking your order; the cache is the tray of pre-made dishes under the heat lamp; the database is the kitchen cooking from scratch. Every layer exists to keep you from waiting on the kitchen — because the kitchen is the slowest, most expensive part, and it can only cook so many meals at once.
Forward vs reverse proxy
A proxy is just a middleman that relays requests. The direction it faces is the whole distinction. A forward proxy sits in front of clients and speaks to the wider internet on their behalf — think a corporate proxy that filters employee traffic, or a VPN. A reverse proxy sits in front of servers and receives traffic on their behalf — the client thinks it is talking to one machine, while the proxy quietly fans requests out to a pool behind it.
Your load balancer and API gateway are reverse proxies — that is precisely their job. Naming this in an interview signals you understand the shape of the architecture, not just the box names.
Why the app tier must be stateless
Here is the load-bearing principle of the whole chapter. The load balancer sends each request to whichever app server is free. If server A remembered that "user 42 is logged in" in its own memory, then a follow-up request routed to server B would find a stranger — the user would appear logged out at random. Worse, you could never add or remove servers safely.
The fix is to make the app tier stateless: each app server keeps no per-user memory between requests. Any request can land on any server and be handled identically. Session state lives outside the app tier — either in a shared store (a session row in Redis, keyed by a cookie) or carried by the client itself in a signed token (a JWT the server verifies without looking anything up).
Statelessness is what makes horizontal scaling possible: to handle more load you just add more identical app servers behind the LB, and to survive a failure you just remove a sick one. Nothing is lost because nothing important lived on the box. This single idea — push state to the edges (client token or shared store), keep the middle interchangeable — is the difference between a system that scales to millions and one that falls over the moment one server reboots.
What senior engineers actually track along the request path:
• North-south vs east-west. Traffic between a client and your system is north-south; traffic between your own services (app → auth service → cache) is east-west. At scale, east-west traffic dwarfs north-south — one user request can fan out into dozens of internal calls.
• Idempotency on the path. Networks drop responses; clients retry. If a retried POST /charge charges the card twice, you have a bug. A safe path makes writes idempotent — an idempotency key so the second identical request is a no-op. (Full treatment in the API chapters.)
• A request ID follows the whole journey. The gateway stamps one ID and every hop logs it, so a single trace reconstructs the entire path when something breaks. This is distributed tracing — the subject of Part VI.
• The DB is sacred; protect it. Every layer above it is, in some sense, a scheme to hit it less.
A concrete walk: loading a Twitter/X timeline
Trace it. You open the app and it requests your home timeline:
- DNS resolves the API host — cached, so ~0 ms — and points at a nearby edge.
- Your avatar, the app's JS bundle, and images load straight from the CDN at the edge. They never reach the origin.
- The dynamic request
GET /timelinecrosses to the load balancer, which picks a healthy app server. - The API gateway verifies your auth token (stateless — no DB lookup needed), checks you are under your rate limit, and stamps a request ID.
- A stateless app server handles it. It asks: is this user's timeline already computed in the cache? For most users, yes — timelines are pre-computed and "fanned out" into a per-user cache when people they follow post. That is a cache hit, answered from Redis in under a millisecond.
- Only on a miss (or for accounts following millions) does it fall through to the database and assemble the timeline live — the slow path.
- The response retraces the hops back to your screen.
The lesson: at scale, the common case is engineered to never touch the database at all. The whole path is a series of increasingly expensive fallbacks, and good design keeps you in the cheap tiers.
The Academy is honest small-scale: a single Next.js + Drizzle/MySQL app on one host. The "request path" collapses almost entirely — DNS points at one server's IP, the Next.js process is both gateway and app tier, and MySQL sits on the same box. There is no separate load balancer, no CDN in front of dynamic routes, no Redis. And that is correct for its scale: adding those layers would be pure cost and complexity for no benefit. But the design still respects the principle that matters — auth is a signed cookie/token, not server memory — so the day traffic justifies a second app server, we can add one behind a load balancer without rewriting how sessions work. The path is short today, but it is shaped to grow.
The path used to be short: one machine ran the web server, the app, and the database together. The layers we now take for granted were each a hard-won response to scale. Akamai commercialised the CDN in 1999 to survive traffic spikes the origin couldn't. The reverse-proxy load balancer became standard as sites outgrew a single box. The push to stateless app tiers accelerated with cloud autoscaling in the 2010s — you cannot spin servers up and down on demand if they hoard user state. Each box in the diagram is a fossil of a scaling crisis someone else already suffered so you don't have to.
Common Mistakes
- Drawing one box for "the server." A senior answer distinguishes the edge (DNS/CDN/LB), the application tier, and the data tier — because they scale, fail, and cost differently.
- Making the app tier stateful. Storing sessions or uploads in a single server's memory or local disk quietly kills horizontal scaling and breaks the moment the LB routes elsewhere. Push state to a shared store or a token.
- Ignoring where latency actually lives. Optimising 2 ms of app code while a cross-region DB round trip costs 150 ms is polishing the doorknob while the house is on fire. Find the dominant hop first.
- Forgetting the CDN for static assets. Serving images and JS bundles from your origin wastes your most expensive capacity on your cheapest, most cacheable content.
- Re-handshaking per request. No keep-alive / no connection pooling means paying the TCP+TLS or DB-connection setup cost every single time — often more than the useful work.
- Assuming retries are free and safe. A retried non-idempotent write double-charges, double-posts, double-sends. The path needs idempotency, not just optimism.
Your users are in Lagos; your only servers are in Virginia. A single intercontinental round trip is ~150 ms. A cold HTTPS request needs a DNS lookup, a TCP handshake (1 RTT), a TLS handshake (1–2 RTTs), then the request itself (1 RTT) — call it 4 round trips before a single byte of your content returns. That is ~600 ms of pure distance, before your code runs at all. Now put a CDN edge in Lagos with keep-alive: the round trips shrink to a few milliseconds, TLS is reused, and static content answers locally. You just cut over half a second without touching a line of application code. Which change would a user notice more — a faster algorithm, or a closer server?
Interview Corner
- Sketch the diagram. From memory, draw the full request path — browser → DNS → CDN → LB → gateway → app → cache → DB — and label what each hop adds in one phrase. Mark which hops a static asset request skips.
- Estimate the numbers. Assume your users are 150 ms (one RTT) from your servers and a cold request needs 4 round trips before content returns. Compute the pre-content latency, then estimate it again with a CDN edge 5 ms away and TLS resumption. Say the saving out loud.
- Say the trade-off out loud. Explain, in one breath, why a stateless app tier scales and a stateful one doesn't — and name the two places session state can live instead.
- Open your browser's DevTools Network tab on any large site, reload, and find the DNS, connection/TLS, and waiting (server) times for the first document request. Compare them to the table in this chapter — reality rarely matches the textbook exactly, and noticing the gap is the skill.
Chapter Summary
- A request at scale is a series of hops — DNS → CDN → load balancer → API gateway → app server → cache → database — and each box was added to solve a specific latency, scale, or reliability problem.
- Latency concentrates in the hops that cross a network or hit disk: intercontinental RTTs (~150 ms), TLS handshakes (multiple RTTs), and DB queries dwarf in-memory cache reads (~1 ms).
- Cut latency by distance and avoidance: CDN/edge, keep-alive and TLS resumption, caching in front of the DB, connection pooling, and co-locating services.
- The load balancer and API gateway are reverse proxies — one front door hiding a pool of servers behind it.
- The app tier must be stateless — session state lives in a shared store or a signed token, never in one server's memory — because that is what makes horizontal scaling and failover possible.
- Distinguish north-south (client↔system) from east-west (service↔service) traffic; at scale, east-west dominates. Make writes idempotent for safe retries, and stamp a request ID for tracing.
We now have the skeleton — the hops a request travels and where the time hides. But we glossed over the most important box for a developer: the app server, and the contract it exposes to the world. How should those endpoints be shaped? Is GET /timeline the right design, or should the client ask for exactly the fields it needs, or call a remote procedure directly? That is the next chapter: Designing APIs (REST, RPC, GraphQL) — the shape of the conversation that flows along the path we just traced.
Editorial reflection — the request path is the first diagram you should be able to draw in your sleep, because every other system-design question is a variation on "which box do we change, and what does that cost the others?" Add a CDN and you cut latency but complicate cache invalidation. Add a replica and you scale reads but invite stale data. Make the tier stateless and you scale freely but push state somewhere you now have to manage. There is no box you can add for free — only trade-offs you choose on purpose. Learn the path not as trivia, but as the map you'll navigate for the rest of your career.
Q&A · 0
Enrol to ask questions and join the discussion.
No questions yet — be the first to ask.