B2 · Part 1 · Beginner
Client-server basics
A senior candidate can narrate everything between the keypress and the pixel — and knows what each hop costs in milliseconds.
08On this page
- The one question that separates levels: "what happens when I hit enter?"
- DNS: four lookups you hope never to make
- TCP and TLS: the cost of a cold connection
- HTTP/1.1 vs HTTP/2 vs HTTP/3, at the level that changes a design
- Load balancer, reverse proxy, API gateway
- Statelessness is the property everything else is built on
- Numbers every engineer should know
- Recognizing it in an unseen problem
The one question that separates levels: "what happens when I hit enter?"
This looks like a trivia question and is actually a depth probe. Anyone can say "the browser sends a request to the server." The signal is in whether you can name each hop, say roughly what it costs, and identify which hops you get to influence as a designer. Every scaling technique in later chapters — caching, load balancing, CDNs, replicas — is an intervention at one specific point on this path. If the path is fuzzy, the interventions sound arbitrary.
DNS: four lookups you hope never to make
The browser needs an IP address before it can open a socket. It asks its
configured recursive resolver (your ISP's, or a public one like
8.8.8.8), and that resolver is the component that does the actual walking:
it asks a root nameserver who handles .com, asks that TLD
server who is authoritative for example.com, and asks the
authoritative nameserver for the record. Each step is a network round trip,
which is why a genuinely cold resolution can cost 100 ms or more, and why
in practice it costs zero — every layer caches.
Caching is governed by the record's TTL. A 60-second TTL means fast failover and heavy query load on your nameservers; a 24-hour TTL means the opposite. The critical property for design: the TTL is a hint, not a contract. Resolvers clamp it, operating systems cache on top of it, and browsers keep their own cache for a minute or two regardless. If you plan to move traffic by changing a DNS record, assume a long tail of clients keeps using the old answer for hours.
The record types you will actually name in a design are few: A and AAAA map a name to an IPv4 or IPv6 address, CNAME aliases one name to another (not allowed at the zone apex, which is why providers invent "ALIAS" records), NS delegates a zone, MX routes mail, and TXT carries verification and policy strings. When a name returns both A and AAAA records, clients using Happy Eyeballs (RFC 8305) start IPv6 first and, if it has not connected after a short delay (RFC 8305 recommends 250 ms), start IPv4 and keep whichever completes first, so a broken IPv6 path costs a fraction of a second rather than a full timeout.
| DNS routing strategy | Mechanism | Reach for this when… |
|---|---|---|
| Multiple A records (round robin) | Return several IPs; clients pick roughly at random | You need crude spread across a handful of static endpoints and nothing better is available |
| Weighted records | Return IP A 95% of the time, IP B 5% | Canary deploys and gradual migrations between stacks or providers |
| GeoDNS / latency-based | Answer depends on the resolver's location or measured latency | Multi-region: send European users to the European stack, so each round trip drops from roughly 80-100 ms to tens of milliseconds or less (a cold connection makes several of them) |
| Health-checked failover | Provider probes endpoints and withdraws dead records | Regional disaster recovery — accept that failover takes TTL plus stubborn-cache time |
| Anycast | One IP announced from many locations; BGP routes each client to the topologically nearest announcement | What CDNs and large services actually use — no client caching problem, and failover is a BGP route withdrawal rather than a TTL expiry. "Nearest" means nearest in network terms, not geographic distance, and route convergence can take from seconds to minutes. A routing shift can also break long-lived TCP connections mid-flight. Requires network-level infrastructure |
So DNS is a load balancer, but a bad one: it balances resolvers rather than requests, it can't see that a server is at 99% CPU, it can't do per-request decisions, and its failover is measured in minutes. It is the right tool for coarse geographic steering and the wrong tool for anything reactive.
⚠ "We'll just lower the TTL to 30 seconds and fail over"
This is the most common wrong answer about DNS in an interview. Aggressive
TTLs help, but a meaningful fraction of clients — corporate resolvers, some
mobile stacks, anything with a broken cache — will keep hammering the dead
IP well past it. If failover must be fast, the address must stay the same:
anycast, a virtual IP that moves, or a load balancer in front.
TCP and TLS: the cost of a cold connection
With an IP in hand the client still cannot send a byte of HTTP. TCP needs a three-way handshake (one round trip before the client can send data), and TLS needs its own negotiation on top: two round trips in TLS 1.2, one in TLS 1.3, and zero on resumption if you accept the replay risk of 0-RTT data.
Two design consequences follow directly. First, connection reuse is not an optimisation, it's the baseline: HTTP keep-alive, connection pools in your service-to-service clients, and pooled database connections all exist to amortise this cost. A service that opens a fresh TLS connection per request has added several RTTs and a public-key operation to every call. A fresh TCP connection is also slow in a second way: congestion control starts it in slow start, with an initial window of about 10 segments (roughly 14 KB, RFC 6928), so a response bigger than that needs extra round trips to ramp up even after the handshakes. A warm, reused connection has already ramped. Second, terminating TLS close to the user matters enormously. A CDN PoP 10 ms from the user absorbs the handshake round trips locally and reuses a warm connection back to origin, which is often a bigger win than caching the content itself.
HTTP/1.1 vs HTTP/2 vs HTTP/3, at the level that changes a design
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP, one request in flight per connection | TCP, many streams multiplexed on one connection | QUIC over UDP, streams are independent |
| Head-of-line blocking | At the HTTP layer — a slow response blocks the connection | Fixed at HTTP layer, still present at TCP layer: one lost packet stalls every stream | Gone — a lost packet stalls only its own stream |
| Handshake | TCP + TLS, 2-3 RTT | Same | 1 RTT combined, 0-RTT on resumption |
| Headers | Plain text, repeated in full every request | HPACK compression | QPACK compression |
| Connection migration | Breaks on network change | Breaks on network change | Survives a Wi-Fi to cellular switch via connection ID |
| Reach for this when… | Simple internal service-to-service; still the default for many proxies to origin | Default for browser traffic and gRPC; many small resources over one connection | Lossy or mobile networks, and latency-sensitive global traffic — the win grows with packet loss |
The practical fallout you should be able to state: because HTTP/1.1 allows one outstanding request per connection, browsers open about six connections per origin, and the old trick of "domain sharding" existed to buy more. On HTTP/2 that trick is actively harmful — it defeats multiplexing and header compression and multiplies handshakes. And HTTP/2's remaining weakness is real: multiplexing many streams over one TCP connection means one dropped packet stalls all of them, which is exactly the case QUIC was built to fix.
Say it like this → "Browser to edge I'd run HTTP/3
with HTTP/2 fallback — our users are mobile and packet loss is where QUIC
pays. Edge to origin I'd keep long-lived HTTP/2 connections so the origin
isn't paying handshake cost per request. Internally, gRPC over HTTP/2 for
the streaming and header compression."
Load balancer, reverse proxy, API gateway
These overlap enough that candidates use them interchangeably and get caught. A reverse proxy is any server that accepts a client connection and makes its own request to a backend on the client's behalf; a load balancer is a reverse proxy whose defining job is distributing across many backends; an API gateway is an L7 reverse proxy that additionally owns cross-cutting concerns — authentication, rate limiting, request shaping, per-route policy. One box often does all three.
| L4 (transport) | L7 (application) | |
|---|---|---|
| Sees | IPs, ports, TCP connections | Full HTTP: method, path, headers, cookies, body |
| Can do | Connection-level distribution, extremely high throughput, near-zero added latency. Passes TLS through untouched, or terminates it (for example an AWS Network Load Balancer TLS listener) without ever seeing HTTP | Path and header routing, retries, rate limiting, request rewriting, sticky sessions, per-request balancing. To read HTTP at all it must terminate TLS (or terminate and re-encrypt) |
| Cost | Cannot see individual requests or per-request errors on a long-lived connection | More CPU, added latency (typically well under a millisecond), and it must hold the TLS keys |
| Reach for this when… | Raw TCP services, databases, extreme packet rates, or you want the backend to see the real client TLS | Essentially all HTTP traffic — the routing and observability are worth the overhead |
Beyond distribution, the load balancer is where three other things live, and naming them unprompted is a strong signal: health checks (active probes plus passive outlier ejection when a backend starts erroring), TLS termination, and the balancing algorithm itself. Round robin is fine when every request costs the same; least-outstanding-requests is materially better when they don't, because it routes away from a backend that has quietly become slow. Consistent hashing is the choice when backends hold a warm per-key cache and you want the same key to land on the same node.
⚠ Don't draw one load balancer box and move on
A single load balancer is a single point of failure, and the interviewer
will ask. The real answer is a pair or a fleet behind a floating virtual IP
or an anycast address, health-checked, with the DNS record pointing at the
address rather than any individual machine. It is also worth saying that a
managed L7 balancer scales itself, but its connection and rules limits are
real quotas you should know exist.
Statelessness is the property everything else is built on
A stateless app server is one where any request can be served correctly by any instance, because no request depends on memory left behind by a previous request on that same box. Every request carries or fetches everything it needs. That single property is what makes horizontal scaling, rolling deploys, autoscaling, and instance failure all boring — you can add, remove, or kill a server without anyone noticing.
Statelessness does not mean the system has no state. It means the state has been moved somewhere purpose-built: a database, a cache, a blob store, or the client itself. The interview question is always where did you put it.
| Where session state lives | Cost | Reach for this when… |
|---|---|---|
| In app server memory + sticky sessions | Losing a node logs users out; deploys are disruptive; load skews toward whichever node holds the busy users; autoscaling barely helps | Almost never in a new design — know it mainly so you can explain why you rejected it |
| Shared session store (Redis, Memcached) | One extra network hop (~0.5 ms) per request; the store becomes a dependency you must make highly available | You need server-side revocation, large session payloads, or session data that changes mid-session |
| Signed token in a cookie (JWT and friends) | No lookup at all, but revocation is genuinely hard and every request pays the token size in bytes | Read-mostly identity claims with short expiry, plus a refresh token you can revoke server-side |
Say it like this → "The app tier is stateless —
session lives in Redis, uploads go straight to object storage, and anything
in process memory is a cache that can be dropped. That means the load
balancer can use plain least-outstanding-requests, I can autoscale on CPU,
and losing an instance costs us the in-flight requests only."
Numbers every engineer should know
You will be asked to justify a latency budget, and the justification has to be built from components. Memorise the orders of magnitude, not the digits. The single most useful mental jump is that each of these tiers is somewhere between 10x and 1,000x slower than the last: nanoseconds in cache and memory (about 1 ns in L1, about 100 ns for main memory), tens of microseconds on SSD, about half a millisecond for a datacenter round trip, and hundreds of milliseconds across the planet. Going from memory to a same-datacenter round trip alone is a factor of about 5,000.
These are rounded planning numbers, not measurements. The original table attributed to Jeff Dean (mutex 25 ns, 1 MB read from memory 250 µs, disk seek 10 ms) describes hardware of roughly that era; Colin Scott's interactive page extrapolates it to today and is where the optimistic 3 µs figure for a sequential memory read comes from. Where the two disagree this table gives a range.
| Operation | Time | What it means for you |
|---|---|---|
| L1 cache reference | ~1 ns | Free; never a design consideration |
| Branch mispredict | ~3 ns | Free |
| Mutex lock/unlock | ~20 ns | Contention, not the lock itself, is what hurts |
| Main memory reference | ~100 ns | An in-process cache hit is thousands of times faster than a network cache hit |
| Read 1 MB sequentially from memory | ~30-100 µs in practice (classic table: 250 µs; 2020 extrapolation: 3 µs) | Serialisation and copying cost as much as the read itself |
| SSD random read | ~16-100 µs | A cache miss to local NVMe is survivable; to a remote DB it is not |
| Round trip in the same datacenter | ~0.5 ms | Every internal service hop costs this at minimum — this is why chatty microservices die |
| Redis GET over the network | ~0.2-1 ms | Dominated by the round trip, not by Redis |
| Simple indexed DB query, warm | ~1-10 ms | Your typical read-path floor |
| HDD seek | ~2-10 ms | Why random I/O on spinning disks shaped a generation of storage design |
| Cross-region RTT (US East to US West) | ~60-70 ms | One synchronous cross-country call eats most of a 100 ms budget, and two blow it |
| Cross-continent RTT (US East to Western Europe) | ~70-90 ms (New York to London is about 65-70) | Speed of light in fibre, not an engineering problem you can optimise |
| Cross-planet RTT (US East to Singapore or India, New York to Sydney) | ~200-230 ms | Multi-region read-local architecture is mandatory, not a nice-to-have |
The budget arithmetic that wins arguments
For a 200 ms p99 page load with a user 80 ms away: 80 ms is gone to the
network round trip before your code runs, leaving about 120 ms. That
assumes a warm connection: on a cold one every handshake round trip costs
another 80 ms and the budget is gone before the server runs (see the dry run
below). Those 120 ms of server time are enough for one cache hit plus one
database query, or about five sequential internal service calls if each does around 20 ms of real work. Sequential dependencies, not slow code, are what
usually eat the budget.
Note that the speed of light in fibre is about 200,000 km/s, so New York to London (5,600 km) has a physical floor near 55 ms round trip. Measured paths on that route run only about 15% above it (around 64 ms); less direct routes run further above their floor. When you say "we cannot make this call synchronous across regions", you are citing physics, and interviewers recognise the difference between that and an opinion.
Dry run: a 200 ms page-load budget for a user 80 ms away
| Hop | Cost | Running total | Budget left of 200 ms |
|---|---|---|---|
| Cold TCP handshake (1 RTT) | 80 ms | 80 ms | 120 ms |
| TLS 1.3 handshake (1 RTT) | 80 ms | 160 ms | 40 ms |
| HTTP request + response (1 RTT) | 80 ms | 240 ms | -40 ms — budget already blown, before a line of server code ran |
| Same request, warm connection: HTTP request + response only (1 RTT) | 80 ms | 80 ms | 120 ms |
| Server does one cache hit (~0.5 ms) + one indexed DB query (~5 ms) | ~5.5 ms | 85.5 ms | ~114.5 ms to spare |
Three round trips for a cold connection cost more than the entire 200 ms budget by themselves — the server never gets to run. The identical request on a warm, reused connection has 120 ms of budget left before the server does anything, which is why connection reuse and edge TLS termination are not tuning knobs but the difference between meeting the budget and missing it entirely.
Recognizing it in an unseen problem
- Any prompt with a latency target ("under 200 ms p99") is asking you to build a budget out of the table above — say where the milliseconds go before proposing optimisations
- "Users are global" means the physics number, ~100-250 ms RTT, is now in every request; the design answer is edge termination and read-local replicas, not faster servers
- If a component holds per-user state in memory, the interviewer will ask what happens when it restarts — decide up front whether that state is durable, reconstructible, or genuinely disposable
- "How would you do a zero-downtime deploy / autoscale this?" is a statelessness question wearing a costume
- DNS is the answer for coarse geographic routing and gradual migrations; it is never the answer for fast failover or per-request balancing — an interviewer probing failover wants anycast or a floating VIP
- Distinguish bandwidth problems from round-trip problems: large media is a bandwidth and CDN problem, chatty APIs are a round-trip problem, and they have completely different fixes
Before you move on
- Can you name each hop in "what happens when I hit enter" and roughly what it costs in milliseconds?
- Can you explain why lowering the DNS TTL alone doesn't give you fast failover, and what actually does?
- Can you compute how many round trips a cold TLS 1.3 connection costs before the first HTTP byte, and explain why connection reuse matters?
- Can you distinguish an L4 from an L7 load balancer, say when you'd choose each, and explain why an L7 balancer always terminates TLS while an L4 one only may?
- Can you explain what "stateless" means for an app server and where you'd move the state instead?
Sources and further reading
- RFC 8446: TLS 1.3 — the 1-RTT handshake and the replay caveats of 0-RTT data
- RFC 9000: QUIC — combined transport and crypto handshake, and connection migration by connection ID
- RFC 9114: HTTP/3 — how HTTP maps onto QUIC streams
- RFC 9293: TCP — the current TCP specification, including required congestion control
- RFC 6928: increasing TCP's initial window — why a fresh connection starts at about 10 segments
- RFC 8305: Happy Eyeballs version 2 — racing IPv6 and IPv4 connection attempts
- AWS: Network Load Balancer TLS listeners — an L4 balancer that can terminate TLS
- Latency numbers every programmer should know — the classic table usually attributed to Jeff Dean (100 ns memory, 250 µs per MB from memory, 10 ms disk seek)
- Interactive latency numbers by year — the extrapolated latency table, labelled as estimates
- New York to London round-trip measurements — roughly 64 ms measured against a 55 ms fibre floor
Claims in this chapter were checked against these sources in October 2026. Figures marked "planning figure" are rules of thumb, not guarantees.
5 questions drawn from 8, scored out of 5. This is practice: it runs in your browser, saves nothing and does not change what you have marked read.
Finished reading?
0 of 36 chapters read in this series