Opening a web page takes a DNS lookup, a TCP handshake, a TLS handshake, and a trip through load balancers, caches, app servers and databases. Follow one real request, measured step by step, and see where the time goes.
When you open https://www.wikipedia.org/, your browser goes through nine steps before the page appears. It finds the server’s address, opens a connection, agrees on encryption keys, and sends a request. Behind that address, the request reaches a load balancer, and often a cache, an app server and a database. Most system design decisions change one of those steps.
This post follows one request from start to finish. We measured the network steps against Wikipedia, so those numbers are real, not textbook values. The figures are interactive. You can step through them, and where a figure has choices, you can change the scenario and watch what changes.
About this series
System design has two halves, and this series covers both:
- High-level design (HLD) decides how the pieces fit together: which services exist, where data lives, how requests are routed, what happens when a machine fails, and how the system grows.
- Low-level design (LLD) decides how the code inside one of those pieces is structured: modules, interfaces, error handling, concurrency, and the patterns that keep a change small.
The low-level parts don’t assume that object-oriented programming is the only way. Each principle is shown in C#, Java, Go and Rust, because they solve the same problems differently. Where a functional approach is a real alternative, we show that too.
The series runs from beginner to advanced. Hard ideas get an “Explain it like I’m ten” section first, then the precise version. Every part ends with interview questions and the sources behind its claims. This first part has no code. It’s the map that the rest of the series zooms into.
Try this first
Before reading on, make a prediction. On a new connection to Wikipedia, the first byte of the page arrived about 220 milliseconds after the connection started. How much of that time was spent at Wikipedia, working on the request?
- Almost all of it, because servers do the real work.
- About half.
- Less than a fifth.
If you’re on a laptop, you can look at a real request yourself:
- Open a private window, so the browser has no open connections and no cache for the site.
- Open the developer tools, go to the Network tab, and load the site.
- Click the first request and open Timing.
You’ll see the phases this post explains: DNS lookup, initial connection, SSL, and waiting for the server. If you just reload a normal window instead, most of those phases show zero, because the browser reuses what it already has. That’s worth noticing too, and we’ll come back to it.
On a terminal, curl prints the same phases:
curl -s -o /dev/null -w 'dns %{time_namelookup}s connect %{time_connect}s tls %{time_appconnect}s first byte %{time_starttransfer}s\n' https://www.wikipedia.org/
Each number is cumulative, counted from the start. For the figures below, we timed each step in a small Python program instead. That let us also walk DNS by hand and time a second request on the same connection, which curl doesn’t do in one command.
We’ll come back to the prediction at the end.
The whole journey
A request for https://www.wikipedia.org/ goes through nine steps, from reading the URL to rendering the page. Step through them, then switch between Cache hit and Cache miss to see how step 7 changes.
One request, start to finish. Choose Cache hit or Cache miss to change step 7; the other steps are the same. The service side is a typical design, simplified, not Wikipedia’s real setup.
Here are those steps in words, in case the animation doesn’t play for you:
- The browser reads the URL and splits it into a scheme (
https), a host (www.wikipedia.org) and a path (/). - A DNS resolver turns the host name into an IP address.
- The browser opens a TCP connection to that address with a three-way handshake.
- A TLS handshake checks that the server really is
www.wikipedia.organd agrees on encryption keys. - The browser sends an HTTP request for
/on hostwww.wikipedia.org. - At the other end, a load balancer passes the request to one of several app servers.
- The app server checks its cache. On a hit, the answer is already there. On a miss, it queries the database, and usually stores the result in the cache for next time.
- The response travels back over the same connection.
- The browser renders the HTML, finds the CSS, JavaScript and images it refers to, and requests those too.
The service side of that picture is a typical design, not Wikipedia’s real one. Large sites add more layers, such as edge sites that cache pages close to users. We’ll see that this matters for our measurement. The browser side is what every HTTPS request does.
Before step 1, a browser checks whether it needs the network at all. If its own HTTP cache holds a fresh copy of the page, or a service worker installed by the site answers the request, none of the steps below happen. The steps that follow are what happens when the network is needed.
Now we’ll go through each step in detail.
Step 1: reading the URL
A URL like https://www.wikipedia.org/wiki/Cat has parts, and each part matters to a different layer:
| Part | Here | Who uses it |
|---|---|---|
| Scheme | https |
The browser: use TLS, on port 443 unless the URL gives another port |
| Host | www.wikipedia.org |
DNS, to find the address; TLS, to check the certificate; HTTP, to say which site the request is for |
| Path | /wiki/Cat |
The server, to decide what you asked for |
One detail matters before anything is sent. Suppose you type http://, or just the name. If the site has told your browser before that it only uses HTTPS, the browser switches to https:// itself, without sending anything first. That instruction is HTTP Strict Transport Security (HSTS, RFC 6797).
HSTS exists because a plain-HTTP request can be intercepted before the site gets the chance to redirect you. It can’t protect the very first visit, before the browser has seen the policy. RFC 6797 calls that the “Bootstrap MITM Vulnerability”. Browsers reduce it with a built-in preload list of sites that always use HTTPS.
Step 2: finding the address with DNS
Computers connect to IP addresses, not names. The Domain Name System (DNS) is the internet’s distributed directory, and it turns www.wikipedia.org into an address like 103.102.166.224.
No single server knows every name. The names form a tree, and each level only knows who is responsible for the level below it:
- The root servers know who runs each top-level domain, such as
.orgor.com. - The
.orgservers know who runswikipedia.org. - Wikipedia’s own name servers know the addresses inside
wikipedia.org.
Your device doesn’t walk that tree itself. It asks a recursive resolver, which is usually run by your internet provider, your company, or a public DNS service. The resolver does the walking.
RFC 1034, the original DNS specification, describes this as two modes:
- In recursive mode, the server you ask chases the answer on your behalf.
- In iterative mode, a server hands back a referral, and the asker follows it.
Your device uses recursive mode with its resolver, and the resolver uses iterative mode with every other server.
We did the walk by hand, with a small program that asks each server directly, the way a resolver with an empty cache would. Step through what happened, then choose Cached on this machine.
One real lookup, done by hand from our machine on 2026-09-15. Choose Cached on this machine to see the same question answered in 1.0 ms. A real resolver sits elsewhere and caches far more, so its times differ; the steps are the same.
The walk, in words:
- A root server was asked for
www.wikipedia.org. It didn’t know the address, but it knew the servers for.org, so it referred us to them. That took 125 ms. - A
.orgserver referred us to Wikipedia’s name servers, in 77 ms. - Wikipedia’s name server answered in 240 ms, but not with an address. It said
www.wikipedia.orgis a CNAME, an alias, fordyna.wikimedia.org. - The lookup started again for the new name. This time it went straight to
.org, because the walk had already learned who runs.org. That server referred us towikimedia.org‘s name servers, in 69 ms. wikimedia.org‘s name server returned the address103.102.166.224, in 236 ms.
That’s five queries and 746 ms before a connection could even start. The two queries to Wikimedia’s name servers were the slow ones. Their copies were further from our machine than the root and .org copies we reached.
A walk like this is rare in practice, because resolvers cache. In the same run, the operating system’s normal lookup took 85 ms, because the resolver it asks had most of the chain cached already. Asked again straight away, it answered in 1.0 ms, from a cache on the machine itself (here, systemd-resolved on Linux).
Our walk is one sample, done from one laptop, so treat its milliseconds as an illustration. A real resolver also works a little differently. Many send each server only the part of the name it needs, a technique called QNAME minimisation (RFC 9156). They choose among a zone’s name servers, and those that validate DNSSEC fetch extra records. The shape of the walk is the same.
Why DNS is fast almost every time
Every DNS answer carries a TTL, a time to live, in seconds. Anyone who receives the answer may cache it for that long. In our walk:
| Record | TTL | How long |
|---|---|---|
The root’s referral to .org |
172,800 s | 2 days |
.org‘s referral to wikipedia.org |
3,600 s | 1 hour |
The alias www.wikipedia.org → dyna.wikimedia.org |
86,400 s | 1 day |
The address of dyna.wikimedia.org |
180 s | 3 minutes |
So a busy resolver almost never asks the root, and rarely asks .org. The record it refreshes most often is the address, with its three-minute TTL.
That short TTL is a deliberate choice. Wikimedia’s name servers use gdnsd with a geoip plugin, which Wikimedia’s own documentation describes as “responsible for geographic DNS”. The address you get depends on where your resolver is, so you may see a different one. A short TTL lets Wikimedia change that answer quickly, for example to move traffic away from a data centre, at the cost of more lookups.
Caches exist at several levels: in the browser, in the operating system, and in the recursive resolver. That’s why the full walk is rare, and why a DNS change isn’t seen everywhere at once.
The root isn’t a single machine either. There are 13 root server identities, run by 12 independent organisations. On 15 September 2026, root-servers.org listed 2,045 instances of them around the world. Each identity has one IPv4 address and one IPv6 address, shared by all its copies. That technique is called anycast (RFC 7094), and internet routing delivers each query to one of the copies. It’s usually a nearby one, but not always: our query to a.root-servers.net took 125 ms.
Explain it like I’m ten
You want to visit a friend called Wiki, but you only know her name, not where she lives.
You ask your parent. Your parent doesn’t know either, but they know how to find out. They phone the city office, which says, “We don’t know houses, but the Org neighbourhood office does.” The Org office says, “Ask Wiki’s family, they’ll know.” Wiki’s family says, “She’s staying at her cousin Dyna’s place.” So your parent phones the Org office again, now about Dyna, and gets sent to Dyna’s family, who give the street address.
Your parent writes the address on a sticky note, with a time after which to check it again. Next time anyone asks, the note answers straight away.
The precise version
Your parent is the recursive resolver. The city office, the neighbourhood office and the families are authoritative name servers for the root, .org, and each domain. “Ask someone else” is a referral, which lists the name servers for the next zone down. “She’s staying at Dyna’s” is a CNAME record. The sticky note is the resolver’s cache, and the time on it is the record’s TTL.
Where the analogy breaks: the offices don’t phone each other. The resolver makes every call itself, and each server only answers the question it’s asked. Also, the notes have separate expiry times for every record. The .org referral stays good for two days, while Wikipedia’s address is checked again after three minutes.
What happens when a name has several addresses
A name often has several addresses, both IPv4 (A records) and IPv6 (AAAA records). Our walk asked for the IPv4 address, while our program actually connected over IPv6.
Clients try the addresses in a way that avoids waiting on a broken one. The approach is called Happy Eyeballs (RFC 8305). The client starts with its preferred address. If that hasn’t connected after a short delay, it starts another attempt alongside it. The RFC recommends 250 ms as a default delay.
Step 3: opening a connection with TCP
With an address, the browser opens a TCP connection. TCP gives both sides a reliable, ordered stream of bytes. Lost packets are sent again, and bytes arrive in the order they were sent.
To set that up, both sides need to agree on sequence numbers, which is how each one tracks which bytes the other has received. That takes a three-way handshake:
- The client sends SYN, with its starting sequence number.
- The server replies SYN-ACK, with its own starting number and an acknowledgement of the client’s.
- The client sends ACK, acknowledging the server’s number.
Why three messages, not two? Each side chooses a starting number, and each side needs to know the other received it. RFC 9293, the current TCP specification, gives a further reason: “The principal reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.” A delayed SYN from an earlier attempt can’t open a connection, because the client won’t confirm it.
The client can send its first data together with the final ACK. So the handshake costs one round trip: the time for a packet to reach the server and for the reply to come back.
We measured it. The median TCP connect took 62 ms, and a ping to the same address took 65 ms. Faster servers can’t shorten that time, because it’s the time packets take to travel. That’s why large services put servers close to their users.
Step 4: making it private with TLS
A TCP connection carries bytes that anyone on the path could read or change. TLS (Transport Layer Security) adds three things:
- Authentication: the server sends a certificate that ties its public key to the name
www.wikipedia.org. A certificate authority signed it, usually through an intermediate certificate. The browser checks that chain up to a root certificate it trusts, from the browser’s or operating system’s built-in list. It also checks that the certificate’s names include the host it asked for, following the rules in RFC 9525. The certificate itself is public, so the server also signs part of the handshake with the matching private key. That signature is what proves the server really holds the key. - Key agreement: both sides compute the same secret keys without ever sending those keys over the network.
- Encryption and integrity: after the handshake, every byte is encrypted, and any change on the way is detected.
The browser’s first TLS message, the ClientHello, also carries two things in plain text:
- SNI (Server Name Indication, RFC 6066) names the host. This lets one IP address serve many sites, each with its own certificate.
- ALPN (RFC 7301) lists the application protocols the browser speaks. The server picks one, for example
h2for HTTP/2, so no extra round trip is needed to decide.
TLS 1.3 needs one round trip, TLS 1.2 needed two
In TLS 1.3 (RFC 8446), the client sends a key share in its very first message, guessing which key-exchange group the server will accept. The server replies in a single flight with:
- its own key share;
- its certificate;
- a signature over the handshake, made with its private key;
- its Finished message.
The client checks all of that, sends its own Finished, and can send the HTTP request right behind it. If the client guessed the wrong group, the server asks it to try again with a HelloRetryRequest, which costs one extra round trip.
A full TLS 1.2 handshake (RFC 5246) couldn’t do that. The client had to wait for the server’s key-exchange parameters before sending its own, and then both sides exchanged Finished messages. That’s a second round trip before any application data.
There were two ways around that in TLS 1.2:
- Resuming an earlier session used a shorter handshake.
- TLS False Start (RFC 7918) let a client send data a little early.
The measurement shows the difference. Choose each connection type, and compare the totals.
Drawn to scale, 1 pixel per millisecond, from the medians of 15 runs on 2026-09-15. Each arrow takes half the measured TCP round trip; only the phase lengths are measured, and the gaps between arrows are the rest of each phase.
The same diagram in words:
- New connection, TLS 1.3: TCP took 62 ms, about one round trip. The TLS handshake took 75 ms: one round trip, plus about 13 ms of other work that our timer can’t split up. That includes both sides’ cryptography and our client checking the certificate chain. Sending the request and receiving the first byte of the response took another 84 ms.
- New connection, TLS 1.2: the handshake took 137 ms, about two round trips. The request took another 86 ms.
- Reused connection: no TCP handshake and no TLS handshake. The first byte came back 67 ms after the request was sent.
The figure draws the median of each phase, from 15 runs of each connection type. We also timed each whole run. A full TLS 1.3 connection, from the start of TCP to the first byte of the response, had a median of 223 ms, and TLS 1.2 had 283 ms. Individual runs varied: the TLS 1.3 handshake took between 66 and 88 ms, and TLS 1.2 between 124 and 162 ms.
What about zero round trips?
TLS 1.3 also has 0-RTT. A client that has talked to the server before can send data in its very first flight, protected by a key from the earlier session. That saves a round trip, but RFC 8446 warns about two things:
- The data “is not forward secret”. Forward secrecy means that stealing a server’s long-term keys later can’t decrypt traffic recorded today. Early data doesn’t have that protection.
- There are “no guarantees of non-replay between connections”. An attacker who captures the first flight can send it again, and the server may accept it twice.
So 0-RTT is only safe for requests that do no harm if they arrive twice, such as a plain GET for public content. It’s never safe for a payment. Servers can refuse early data, or answer 425 Too Early (RFC 8470) to make the client send it again after the handshake.
0-RTT over TCP still needs the TCP handshake first. So it removes the TLS round trip, not all of them.
QUIC (RFC 9000) is the transport under HTTP/3. It runs over UDP, and it combines the transport and TLS 1.3 handshakes into one. So in the common case, a new connection needs one round trip in total instead of two. It can take more:
- A server may first ask the client to prove its address with a Retry packet.
- Until the client’s address is confirmed, a server may send at most three times as much data as it has received. A large certificate chain may not fit in that limit.
Browsers also usually learn that a site supports HTTP/3 from an Alt-Svc header (RFC 9114) in an earlier response, or from a DNS record. So a first visit often starts over TCP. Part 3 covers QUIC and HTTP/3 in depth.
Step 5: sending the HTTP request
With a secure connection open, the browser finally asks for the page. In HTTP/1.1, the request is text:
GET / HTTP/1.1
Host: www.wikipedia.org
User-Agent: Mozilla/5.0 (...)
Accept: text/html
Accept-Encoding: gzip, br
- The method and the target:
GET /asks for the resource at/. The method says what kind of action the request is. - Safe and idempotent: HTTP’s semantics, in RFC 9110, define
GETas safe, meaning it’s only a read. They also define it as idempotent, meaning that sending it twice has the same intended effect as sending it once. Idempotency decides whether a client can retry a request automatically after a failure. That idea comes back throughout this series. Whether a response can be cached is a separate rule, decided by the method and the response’s headers. - The host: one IP address can serve many sites, so the request names the one it wants. RFC 9112 says: “A client MUST send a Host header field (Section 7.2 of [HTTP]) in all HTTP/1.1 request messages.”
- The other headers: they describe what the client accepts, such as formats and compression. They also carry cookies and credentials.
A browser talking to Wikipedia would negotiate HTTP/2, not HTTP/1.1. HTTP/2 and HTTP/3 carry the same methods, status codes and headers in a binary format, and they send many requests at once over one connection. There are a few differences in the details. For example, the host goes in a field called :authority instead of Host (RFC 9113), and headers that only make sense for one connection aren’t allowed. That’s why HTTP’s meaning is defined once, in RFC 9110, separately from each version.
The response has the same shape:
- a status code, such as
200 OK,404 Not Foundor503 Service Unavailable; - headers;
- a body.
Headers such as Cache-Control tell the browser, and any cache along the way, how long the response can be reused.
Step 6: inside the service
Behind a large site’s address, there’s rarely one machine. A request passes through several layers, and each one exists for a reason.
The load balancer
The address DNS returned usually belongs to a load balancer, not to an application server. In our case, the address resolves back to the name text-lb.eqsin.wikimedia.org: a Wikimedia load balancer at one of its edge sites.
A load balancer spreads requests across a pool of servers. It stops sending requests to a server that fails its health checks. That’s how you can add capacity, or take a server out for an update, without users noticing.
Load balancers come in two broad kinds:
- A layer 4 (transport) balancer picks a backend once per connection, then passes the bytes along without reading them. Every request on that connection reaches the same backend. It can still route by the SNI name in the ClientHello, because that’s sent in plain text.
- A layer 7 (application) balancer understands HTTP. It can route each request by path, header or cookie. It usually terminates TLS, which means the encrypted connection ends at the balancer.
Part 30 covers load balancing algorithms and their trade-offs.
The app server
The app server runs your code. For each request, it typically:
- parses and validates the input;
- works out who the caller is, and whether they’re allowed to do this;
- runs the business logic;
- reads or writes data;
- builds the response.
How that code is organised is low-level design, and Stage 2 of this series covers it. For now, notice one property that matters for high-level design.
If an app server keeps no per-user or per-session state that a later request depends on, any server can handle any request. That’s called being stateless. It can still keep things like connection pools and small in-memory caches, because no request needs them to be on a particular server. Statelessness is what makes the load balancer’s job simple.
The cache and the database
The database is the source of truth. It stores data durably and answers queries. A query can be slow, and the database is usually the hardest part to scale.
A cache keeps copies of recent answers in memory, so a repeated request skips the database. On a cache hit, the answer comes back without the database doing any work. On a cache miss, the app queries the database and usually stores the result for next time. That’s the difference between the two scenarios in the journey figure.
Caches don’t only sit next to app servers. Large sites also cache whole responses at edge sites near users, so that many requests never reach an app server at all. Caching trades freshness for speed, because a cached answer can be out of date. Part 22 covers how to choose what to cache and how to keep it correct.
Step 7: the response, and what the browser does next
The response travels back through the same layers. The browser reads the HTML, and while parsing it, finds more resources: stylesheets, scripts, fonts and images. Each one is another request.
Connection reuse pays off here. HTTP/1.1 connections are persistent by default (RFC 9112), so a later request on the same connection skips the TCP and TLS handshakes. HTTP/1.1 sends one request at a time on a connection, though, so browsers open several connections to the same host in parallel, and each new one pays the handshakes. HTTP/2 sends many requests at once over a single connection.
Resources on a different host are a separate case. Wikipedia’s page loads images from upload.wikimedia.org, for example, and that needs its own DNS lookup and handshakes. The exception is HTTP/2’s connection reuse: a browser may send requests for another host over a connection it already has, if that server is authoritative for both names (RFC 9113).
In our measurement, a request on a reused connection got its first byte after 67 ms. A request on a new TLS 1.3 connection took a median of 223 ms.
Where the time went
On a new TLS 1.3 connection to Wikipedia, the first byte of the page arrived a median of 223 ms after the connection started. That’s the number from the prediction at the start, and here’s how the phases split. The table uses each phase’s median, so the phases don’t add up exactly to the median of whole runs:
| Phase | Median | Mostly spent on |
|---|---|---|
| DNS | 1.0 ms | Nothing: this machine had the answer cached |
| TCP handshake | 62 ms | One round trip across the network |
| TLS 1.3 handshake | 75 ms | One round trip, plus cryptography and certificate checks |
| Request to first byte | 84 ms | One round trip, plus Wikipedia’s own time |
We can estimate Wikipedia’s own time by taking away the network round trip. Use the TCP connect time, 62 ms, as the round trip, because it’s measured with the same protocol:
- On the reused connection: 67 − 62 is about 5 ms.
- On a new connection: 84 − 62 is about 22 ms.
We don’t know why the first request on a new connection takes longer. Some per-connection setup at the edge is a likely cause, but our measurement can’t show it.
Either way, that’s well under a fifth of the total, so answer 3 was right. It’s worth being clear about what answered, though. The address belonged to a Wikimedia edge site, and its fast reply most likely came from a cached copy of the page there, not from an app server running code against a database. A request that needs real work behind the edge takes longer. The handshakes would cost the same.
For a new connection to a nearby edge, the network costs more than the server’s work, and handshakes are most of that. This is why so many system design decisions are about avoiding round trips:
- reusing connections;
- putting servers and caches near users;
- caching at every layer;
- keeping the number of calls behind one request small.
Why this matters for design
Each step on the path is a decision point, and later parts of the series come back to each one:
- Distance is latency. A round trip to a distant region costs tens or hundreds of milliseconds before any code runs. That’s why services use multiple regions and edge sites, which Part 38 covers.
- Every hop is a place to fail. DNS, the load balancer, an app server, the cache and the database can each be slow or down. A design has to say what happens when each one fails (Parts 23 and 33).
- Caches exist at every layer: the browser, DNS, edge sites, the application and the database. Each one saves time and adds a way to serve stale data (Part 22).
- Connections are expensive, so reuse them. This holds between your own services too, not just for browsers.
- Statelessness makes scaling simpler. If any server can take any request, adding app servers adds capacity, until a shared part such as the database becomes the limit (Part 31).
Trade-offs along the path
Short DNS TTLs versus lookup cost. A short TTL lets you move traffic quickly during an incident or a migration. It also means more lookups and more load on your name servers, and it only helps if clients respect it.
Where TLS terminates. Terminating at the load balancer keeps certificates in one place and lets it route by path or header. But traffic behind the balancer is plain text, unless it’s encrypted again. Many organisations now encrypt inside their own networks too, an idea covered under zero trust in Part 36.
Long-lived connections versus even load. Reusing connections saves handshakes. But a layer 4 balancer picks a backend once per connection, so a few clients with long-lived connections can load some servers more than others.
Caching versus freshness. A cache hit is fast, but it might be out of date. Decide and write down how stale each kind of data is allowed to be, as a requirement. Part 2 is about turning requirements like that into numbers.
Common mistakes
Creating a new HTTP client for every call. Each new client can mean a new connection pool, so every call pays the TCP and TLS handshakes again. Closed connections also hold on to local ports for a while, and under load a busy service can run out of them. The fix is the same idea in every language:
- C#: reuse
HttpClient. Microsoft’s guidelines warn that creating clients per request can exhaust ports. They recommend a long-lived client withPooledConnectionLifetimeset, orIHttpClientFactory. - Java: create one
java.net.http.HttpClientand share it. Its documentation says an instance “typically manages its own pools of connections”. - Go: reuse an
http.Client, and always close the response body. Go’s documentation says the transport “may not reuse HTTP/1.x “keep-alive” TCP connections if the Body is not read to completion and closed”. Since Go 1.27, it adds that closing the body also reads it to completion in the background, up to a limit. So closing is enough in most cases. - Rust: with
reqwest, create oneClientand reuse it. Its documentation says it “holds a connection pool internally”.
Holding on to DNS answers forever. A long-running process that resolves a name once keeps talking to the old address after a migration.
- .NET:
HttpClientonly resolves DNS when it opens a connection. That’s whyPooledConnectionLifetimeexists: it retires connections after a while, so the name is looked up again. - Java: the JDK caches successful lookups for 30 seconds by default, as its
java.securityconfiguration file says.
Changing a DNS record with a long TTL at the moment of migration. Clients may keep the old answer for the whole TTL. RFC 1034 gave the fix in 1987: “If a change can be anticipated, the TTL can be reduced prior to the change to minimize inconsistency during the change, and then increased back to its former value following the change.”
Measuring only server time. Your monitoring says requests take 15 ms, while users far away wait 400 ms. Measure from where your users are, too.
Using TLS 0-RTT for requests that change things. Early data can be replayed. Allow it only for safe, idempotent requests, or turn it off.
What LLD and HLD each decide here
Nearly every choice in this post is high-level design:
- how many app servers there are;
- where TLS terminates;
- whether there’s a cache, and where;
- which sites serve which users;
- what the DNS TTLs are.
Low-level design happens inside the app server box:
- how the handler code is split into parts;
- how it reports errors;
- how it talks to the cache and the database without tying the business logic to either one;
- how it stays correct when many requests run at once.
Stage 2 opens that box.
Interview questions
Questions about the request path come up in system design interviews at every level. Try answering each one out loud before you open the answer.
1. What happens when you type a URL into a browser and press Enter?
Show a strong answer
Walk through the layers in order, and say what each one is for:
- Local checks: the browser parses the URL. If the site has sent an HSTS policy before,
httpbecomeshttps. If the HTTP cache or a service worker can answer the request, the network isn’t needed at all. - DNS: the browser, operating system and resolver caches come first. A recursive resolver then follows referrals from the root, to the top-level domain, to the domain’s authoritative servers. Mention TTLs, CNAMEs, and
AandAAAArecords. - TCP: a three-way handshake, taking one round trip. Happy Eyeballs chooses between IPv6 and IPv4.
- TLS 1.3: a ClientHello with SNI, ALPN and a key share, then the server’s certificate, signature and Finished. That’s one round trip, unless a HelloRetryRequest adds another. The browser checks the certificate chain and the host name.
- HTTP: the method, path, host and headers. ALPN usually picks HTTP/2, which sends many requests over one connection.
- Server side: a load balancer or edge site, possibly a cache, stateless app servers, and a database.
- Response and rendering: status, headers and body, then more requests for the page’s resources, reusing connections where possible.
A strong answer also says where the time goes. On a new connection to a nearby edge, the handshakes usually cost more than the server’s processing.
Likely follow-ups: “Where would you cache?” (question 6 below), and “What changes with HTTP/3?” (QUIC combines the handshakes; Part 3).
2. Why can a DNS change take hours to reach everyone, and how do you plan a migration around it?
Show a strong answer
Resolvers, operating systems and applications cache DNS answers for up to the record’s TTL, and some keep them longer than they should. Until an answer expires, they keep using the old one. Some resolvers also enforce a minimum TTL, and a name that didn’t exist can be cached as “doesn’t exist” for a while.
To plan a migration:
- Lower the TTL, for example to 60 seconds, at least one old TTL before the change, so the long-lived cached copies expire first.
- Keep the old servers running and serving traffic during the change.
- Make the change, watch traffic move, and keep watching the old servers until their traffic stops.
- Raise the TTL again.
Likely follow-up: “What about clients that ignore TTLs?” Keep the old endpoint working, or redirecting, until traffic to it actually stops. Measure that rather than assuming it.
3. How many round trips happen before a browser can send an HTTPS request on a new connection?
Show a strong answer
Ignoring DNS:
| Setup | Round trips before the request |
|---|---|
| TCP + full TLS 1.2 handshake | 3: one for TCP, two for TLS |
| TCP + TLS 1.2 with False Start, or with session resumption | 2 |
| TCP + TLS 1.3 | 2: one for TCP, one for TLS (plus one if a HelloRetryRequest is needed) |
| TCP + TLS 1.3 with 0-RTT | 1: TCP still needs its handshake |
| QUIC (HTTP/3) | 1 in the common case; more with a Retry, or if the anti-amplification limit delays the server’s certificate |
| QUIC with 0-RTT | 0: the request goes in the first flight |
| A reused connection | 0 |
The response then needs one more round trip, plus the server’s own time. With 0-RTT, the request can be replayed, so it’s only for safe requests.
Likely follow-up: “Why is 0-RTT dangerous?” (question 5).
4. Why does TCP use a three-way handshake and not a two-way one?
Show a strong answer
Each side picks an initial sequence number, and each side needs to know the other received it. With only SYN and SYN-ACK, the server would never learn that the client got the server’s number.
The third message also protects against old, delayed SYNs. If an old SYN arrives, the server replies, and the client isn’t expecting that connection. So the client resets it instead of confirming it. RFC 9293 gives this as “the principal reason for the three-way handshake”.
Likely follow-up: “What is a SYN flood, and how do SYN cookies defend against it?” A SYN flood sends many SYNs and never completes the handshake, which fills the server’s table of half-open connections. SYN cookies avoid storing anything: the server encodes the connection details in its own sequence number, and rebuilds them from the client’s final ACK. Part 36 covers attacks like this.
5. What is TLS 1.3 0-RTT, and when should you not use it?
Show a strong answer
0-RTT lets a returning client send application data in its first flight, protected by a key from a previous session. That saves a round trip.
It has weaker guarantees. RFC 8446 says early data isn’t forward secret, and it can be replayed: an attacker can capture the first flight and send it again. So:
- Allow it only for safe, idempotent requests, like
GETfor public content. - Never allow it for payments, orders, or anything that changes state.
- Servers can reject early data, accept it only on chosen endpoints, or answer
425 Too Early(RFC 8470) so the client sends the request again after the handshake. RFC 8446 also describes server-side replay defences, such as single-use tickets and recording ClientHellos. - Over TCP, 0-RTT still needs the TCP handshake first.
Likely follow-up: “How would you make a payment endpoint safe to retry anyway?” With idempotency keys, which Part 4 covers.
6. Where along the request path can you add caching, and what does each layer save and risk?
Show a strong answer
| Layer | Saves | Risk |
|---|---|---|
Browser (HTTP cache, Cache-Control) |
The whole request while fresh; after that, a 304 Not Modified revalidation saves sending the body again |
Users see stale content until it expires. You can only ask browsers to clear it (the Clear-Site-Data header) when they next talk to you |
| DNS | Lookups | Slow changes during a migration |
| Edge site / CDN | A round trip to the origin, plus origin load | Stale content, or content shared wrongly, such as one user’s private page served to another |
| Reverse proxy | App server work | Same as an edge cache, closer to the origin |
| Application cache | Database queries | Staleness, invalidation bugs, a stampede of requests when a popular entry expires |
| Database buffer cache | Disk reads | Mostly handled by the database, and limited by memory |
A strong answer ties each layer to a freshness requirement: how stale can this data be?
Likely follow-ups: “How do you invalidate?” and “What’s a cache stampede?” Part 22 covers both.
7. Should TLS terminate at the load balancer or at the application servers?
Show a strong answer
It depends on what the design needs:
- At the load balancer: certificates live in one place, and the balancer can route by path, header or cookie (layer 7). App servers skip the cryptography. But traffic from the balancer to the app servers is plain text, unless it’s encrypted again.
- Re-encrypting to the backends: one TLS connection ends at the balancer, and another runs from the balancer to each app server. This gives layer 7 routing and encryption inside the network, at the cost of more certificates to manage.
- Pass-through (layer 4): TLS ends at the app server. The balancer can’t see the HTTP request, though it can still route by SNI. Traffic stays encrypted all the way to the server.
Zero trust guidance, such as NIST SP 800-207, says communication should be secured “regardless of network location”. That argues for encrypting inside the network too (Part 36).
Likely follow-up: “What is mutual TLS?” It’s TLS where the client also presents a certificate, so both sides prove who they are. It’s common between services, and Part 36 covers it.
8. Your API’s server-side latency is 20 ms, but users in another region see 400 ms. How do you find out why?
Show a strong answer
Break the 400 ms into phases, measured from the users’ side: DNS, TCP connect, TLS, time to first byte, and download. Browser timing APIs, synthetic checks from that region, or curl -w all give these numbers. Then read them:
- Big connect and TLS times: the handshakes are crossing a long distance. Check whether connections are reused and HTTP/2 is used. Consider serving that region from nearer: an edge site that terminates TLS, or a regional deployment.
- Users sent to the wrong place: check which site DNS or anycast actually sends those users to. Geographic routing that picks a distant site is a common cause.
- Big DNS time: the resolver or the authoritative servers are far away or slow.
- Time to first byte far above one round trip plus 20 ms: something between the edge and your server is adding time. Look at queueing in front of the app, a proxy, cold caches in that region, or an edge that opens a new connection to your origin for every request.
- Big download time: the response is large, compression is missing, or packets are being lost. A large response on a new connection is also slowed by TCP ramping up its sending rate, which Part 3 covers.
The key idea is that server time isn’t user time.
Likely follow-up: “How would you serve that region without running a full copy of the system there?” Edge caching and TLS termination near users, with the origin staying where it is. Part 38 covers the options.
Sources
- IETF, RFC 1034: Domain Names, Concepts and Facilities (recursive and iterative modes, TTLs)
- IETF, RFC 9156: DNS Query Name Minimisation to Improve Privacy
- IETF, RFC 9293: Transmission Control Protocol (the three-way handshake)
- IETF, RFC 8446: TLS 1.3 (the handshake, HelloRetryRequest, 0-RTT)
- IETF, RFC 5246: TLS 1.2 and RFC 7918: TLS False Start
- IETF, RFC 6066: TLS Extensions (Server Name Indication) and RFC 7301: ALPN
- IETF, RFC 9525: Service Identity in TLS
- IETF, RFC 9110: HTTP Semantics, RFC 9112: HTTP/1.1, RFC 9113: HTTP/2 and RFC 9114: HTTP/3
- IETF, RFC 9000: QUIC and RFC 8470: Using Early Data in HTTP
- IETF, RFC 6797: HTTP Strict Transport Security, RFC 8305: Happy Eyeballs Version 2 and RFC 7094: Architectural Considerations of IP Anycast
- NIST, SP 800-207: Zero Trust Architecture
- Root Server Technical Operations Association, root-servers.org (operators and instance count, checked 15 September 2026)
- Wikimedia, Wikitech: DNS (gdnsd with the geoip plugin)
- Microsoft, HttpClient guidelines for .NET
- Oracle, java.net.http.HttpClient, Java SE 25, and the JDK’s
conf/security/java.securityfile (networkaddress.cache.ttl) - The Go project,
net/httpResponse.Body (Go 1.26 and Go 1.27 wording) reqwest,Clientdocumentation- gRPC, gRPC Load Balancing (layer 4 and layer 7 balancers)
- Measurements:
system-design/checks/part01_request_timing.py, run on 15 September 2026 againstwww.wikipedia.org, 15 runs per connection type, from one location, over HTTP/1.1. Network times depend on where you run it.
What to remember
- A new HTTPS connection needs a DNS answer (usually cached), a TCP handshake (one round trip), and a TLS handshake (one round trip with TLS 1.3, two with a full TLS 1.2 handshake) before it can carry a request.
- DNS is a tree of referrals from the root down, made fast by caching. Each record’s TTL decides how long a change takes to be seen.
- On a new connection to a nearby server, round trips cost more than a fast server’s work. Reusing connections removes the handshakes.
- Behind one address, there’s usually a load balancer, and often edge caches, stateless app servers and a database.
- Every hop can be slow, can fail, and can cache. System design decides what happens at each one.
Every round trip costs time before any code runs, so good designs avoid the ones they don’t need.