System Design Networking
HTTP Deep Dive
You open ChatGPT, type a message, and watch the answer stream token by token. Or you refresh Instagram and a dozen images start loading together. Both stories speak : request in, response out, even when the wires underneath change.
, , and mostly fight wasted , , and fat headers while keeping familiar methods and status codes.
What each generation fixes
Keep-alive helps, but one queue still waits per connection.
Many streams plus compressed headers share one TCP pipe.
Same HTTP meaning on QUIC over UDP for lossy paths.
Encrypt the channel; newer TLS starts with fewer waits.
What actually happens
is the usual language of browsers calling APIs and servers returning HTML or JSON. Each interaction is a request (method, path, headers, optional body) and a response (status code, headers, body). adds so that conversation stays private.
Moving from HTTP/1.1 to HTTP/2 to HTTP/3 typically improves , header efficiency, and handshake latency, especially on lossy or mobile networks. Your REST resource naming usually stays the same; the transport behavior changes. Setup timing and capacity math live in Numbers to know.
Before the wire-format details, decide which version your edge should offer and when HTTP/3 is worth enabling.
How to choose an HTTP version
1. What do your clients actually negotiate? Enable newer versions at the CDN or edge first. Clients fall back through or .
2. Is the pain many small resources or packet loss? helps page loads with many assets. helps when mobile loss or handshake latency dominates.
3. Do you need universal tooling? Internal microservices and debugging still lean on HTTP/1.1 simplicity; public SPAs and gRPC often default to HTTP/2.
Simple rule of thumb
Use as the default for web clients and many APIs. Enable on the CDN/edge for mobile users. Do not pitch HTTP/3 as a rewrite of your API: methods and status codes stay the same. Exact setup floors live in Numbers to know.
HTTP semantics vs transport
Semantics stay stable
GET, POST, status codes, cache headers, and content negotiation still mean the same thing across versions.
Transport changes
HTTP/2 adds multiplexed streams over TCP. HTTP/3 uses QUIC over UDP to reduce setup and packet-loss stalls.
0-RTT caution
0-RTT is useful for resumed safe requests, but avoid it for unsafe operations that could be replayed.
Payload and bandwidth budget
Header overhead
Assume each request carries 2 KB of headers and the service receives 50,000 requests per second. Multiplying bytes per request by requests per second gives 100 MB/s of request headers alone, using decimal units. This excludes response bodies and lower-level overhead. Reducing a cookie helps repeatedly because the browser may attach it to every matching request.
Field trimming
8KB response trimmed to 800B saves 7.2KB. Across 1B reads, that is about 7.2TB less transfer.
Compression
100KB JSON compressed to 20KB saves 80KB. Across 10M responses, that is about 800GB less egress.
Fan-out
One 600B event to 10K subscribers becomes 6MB of outbound payload, before protocol and encryption overhead.
With the choice framed, a short history shows why each generation existed without changing what GET or POST means.
HTTP: tested over decades
HTTP has been refined across 30+ years of real-world internet traffic. Each version solved specific pain points while keeping backward compatibility.
1991
HTTP/0.9
GET only, no headers
1996
HTTP/1.0
Headers, status codes
1997
HTTP/1.1
Keep-alive, chunked
2015
HTTP/2
Multiplexing, binary
2022
HTTP/3
QUIC, 0-RTT
Why HTTP won: simplicity (easy to debug), universality (every browser, proxy, and CDN speaks it), and extensibility (headers add features without breaking old clients).
Next, read a URL the way the stack does: scheme, host, path, query, then the verb that says what to do.
URL anatomy and HTTP verbs
Every part of the URL means something
A URL is not just a string: every segment tells the server what you want, where it lives, and how to find it.
Scheme
https: protocol to use
Host
api.example.com: DNS resolves this to an IP
Port
:443: default for HTTPS (80 for HTTP)
Path
/v2/users/123: resource location
Query
?fields=name,email: parameters/filters
Fragment
#section1: client-side only, never sent to server
HTTP verbs: what you want to do
Safe methods are intended for retrieval without a requested business-state change; incidental logging is still possible. Idempotent methods have the same intended effect when repeated as when applied once. A second DELETE can return a different response while the resource remains deleted, so identical responses are not required. POST is not generally idempotent, but an application can add an idempotency contract for a particular operation.
The method (verb) tells the server what action to perform on the resource. It goes in the first line of the request:
| Verb | Action | Has body? | Example |
|---|---|---|---|
| GET | Read / fetch resource | No | GET /users/123 |
| POST | Create new resource | Yes | POST /users |
| PUT | Replace entire resource | Yes | PUT /users/123 |
| PATCH | Partial update | Yes | PATCH /users/123 |
| DELETE | Remove resource | No | DELETE /users/123 |
| HEAD | GET but no body (headers only) | No | HEAD /users/123 |
| OPTIONS | What methods are allowed? | No | OPTIONS /users |
HTTP status codes that matter
| Class | Meaning | Examples |
|---|---|---|
| 2xx Success | Request handled as intended. | 200 OK, 201 Created, 204 No Content |
| 3xx Redirect | Look elsewhere (or use cached). | 301/302 redirect, 304 Not Modified |
| 4xx Client error | Fix the request, auth, or rate of calling. | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests |
| 5xx Server error | Server or upstream failed; client may retry carefully. | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout |
429 Too Many Requests
Tell clients they hit a quota. Pair with Retry-After when you can. At the edge, returning 429 is a standard control for abuse and noisy neighbors.
Simple rule of thumb
Pick codes that match ownership: bad input → 4xx; overloaded dependency → 503; auth missing vs forbidden → 401 vs 403. Consistent error bodies plus status codes beat inventing string-only failures.
Status codes answer outcome. The request and response lines below show where methods, headers, and bodies sit on the wire.
Request, response, and negotiation
Request and response anatomy
The method describes the requested operation, the target identifies the resource, and headers carry metadata about representation, authorization, and intermediaries. A status code describes the outcome at the HTTP boundary; the body contains the representation or error details. This separation lets caches and proxies make useful decisions without understanding every application field, provided the application uses the semantics consistently.
Every HTTP interaction is a request (what the client wants) and a response (what the server sends back). Headers carry metadata about the message itself.
HTTP request
GET /api/v2/users/123 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJI...
Accept-Encoding: gzip, br
User-Agent: MyApp/2.1
Cache-Control: no-cacheRequest line: method, path, HTTP version
Host: which server (required in HTTP/1.1; enables virtual hosting)
Accept: what format I understand
Authorization: who I am
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 245
Cache-Control: max-age=3600
ETag: "abc123def"
X-Request-Id: req_8f3k2j
{ "id": 123, "name": "Alice", "email": "a@b.com" }Status line: version, status code, reason phrase
Content-Type: response format
Cache-Control: how long to cache
: fingerprint for conditional requests
Headers describe content, control caching, handle authentication, and enable features without changing the URL or body. Common groups: Authorization / Cookie;Cache-Control / ETag;Content-Type / Content-Length;Accept-Encoding / Content-Encoding.
Client and server agree on the response format through headers: the same URL can return JSON, HTML, or XML depending on what the client asks for.
Client says: here is what I understand
Accept: application/json
Accept: text/html
Accept: application/xml, application/json;q=0.9
Accept-Language: en-US, hi;q=0.8
Accept-Encoding: gzip, brServer says: here is what I am sending
Content-Type: application/json; charset=utf-8
Content-Language: en-US
Content-Encoding: brGET /users/123 with Accept: application/json may return {"id":123,"name":"Alice"}; a browser asking for text/html gets markup instead. Clients advertise decompressors; servers pick one and set Content-Encoding. Older clients that omit brotli still work. Compression is content negotiation: cache keys and CDNs must treat differently encoded bodies as distinct variants when appropriate.
Headers, HTTPS, and trust boundaries
Headers are flexible
HTTP header fields let a message carry metadata in addition to its body. An endpoint generally ignores a field it does not understand, but proxy forwarding and removal follow protocol rules and local configuration. Do not assume a custom identity or tracing field survives every intermediary. Content negotiation via Accept and Accept-Encoding is the classic lesson: the same URL can return different encodings or formats.
HTTPS = HTTP over TLS
TLS encrypts bytes in transit and authenticates the server (and optionally the client). Prefer HTTPS everywhere on the public internet; still validate tokens and permissions inside the app: encryption is not authorization.
Never trust the body for authz
Do not authorize with a userId from the JSON body alone. Derive the caller from the auth context (verified session, JWT subject, mTLS identity) and check that identity may act on the resource.
Authz and trust aside, the three HTTP generations differ mainly in how connections, streams, and loss recovery work.
Protocol generations side by side
: the workhorse
One request at a time per connection unless you open many parallel TCP pipes; simple but easy to leave bandwidth idle.
- Keep-alive reuses one TCP socket so later requests skip a fresh handshake.
- Pipelining is often disabled because FIFO responses still serialize wait time.
- Chunked transfer streams the body so large replies need not be fully buffered first.
- lets one slow response stall later replies; browsers open ~6 connections per domain.
- Plain text headers repeat cookies and auth, so 1-2KB of bloat per request is common.
Cold setup cost is summarized in Numbers to know.
: multiplexed streams
Many logical streams ride one TCP session with compressed headers, though TCP itself can still block everything on one lost packet.
- interleaves streams on one TCP connection so pages need fewer parallel sockets.
- compresses repeated headers with tables and Huffman, often cutting 85–95% of header bytes.
- Server Push was removed from Chrome in 2022; prefer 103 Early Hints for critical assets instead.
- Stream prioritization uses weights and dependencies so browsers can prefer CSS and scripts over images.
: and UDP
QUIC rebuilds streams atop UDP so mobility, loss recovery, and TLS integrate without inheriting every TCP limitation.
- QUIC runs over UDP with mandatory TLS 1.3 so encryption starts with the transport itself.
- A connection ID survives IP change, which keeps mobile handoffs from restarting the session.
- lets resumed clients send early data; weigh replay risk on non-idempotent writes.
- Each QUIC stream recovers loss alone, so one lost packet does not freeze every stream.
Search, video, edge, and mobile fleets report better rebuffer, TTFB, and tail latency where UDP/443 is open; timing floors are in Numbers to know.
Version comparison
HTTP/2 interleaves frames from several streams on one TCP connection. This removes the application-level response ordering restriction of HTTP/1.1 pipelining, but TCP still withholds later bytes after a missing segment. HTTP/3 uses QUIC streams so a loss on one stream need not block delivery on another. Neither version removes database latency, shared congestion, or the need for application deadlines.
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Transport | TCP | TCP | QUIC (UDP) |
| Multiplexing | No | Yes | Yes (no TCP HOL) |
| Header compress | No | HPACK | QPACK |
| HOL blocking | HTTP + TCP | TCP only | None at TCP layer |
| Connection setup | 1–3 RTTs | 1–2 RTTs | 0–1 RTTs |
Fit to product: HTTP/1.1 for simple internal REST and curl debugging; HTTP/2 as the default for SPAs, gRPC, and gateways; HTTP/3 when mobile loss or handshake latency dominates (see How to choose).
Where QUIC fits in the story
QUIC provides reliable streams, congestion control, and encryption over UDP. HTTP/3 uses these streams to carry HTTP requests and responses. The useful distinction is between losing data within one stream and making unrelated streams wait for that same loss.
What it is
Reliable, shared, encrypted streams over UDP. HTTP/3 is normal HTTP meaning riding on QUIC instead of TCP.
Why it helps
Faster return visits, better behavior when a phone changes networks, and less freezing across unrelated downloads.
What not to do
Do not redesign everything around QUIC. Turn it on at the edge when useful, and keep most API design on plain HTTP semantics.
Interview answers often need the numeric floors behind those version rows. Keep the table in one place and cross-ref it.
Numbers to know
At 100ms , rough connection-setup floors before the application response (order of magnitude; your path may differ):
- Cold TCP plus TLS 1.2 often costs about three RTTs before the first request byte.
- Warm reuse or HTTP/3 / collapses setup toward roughly 0-1 RTT of wait.
- At 50K QPS of short calls, saving ~200ms of setup is handshake capacity you keep.
- HTTP/2 often tunes max concurrent streams around 100-128; validate your reverse proxy defaults.
Where time goes in one request
A user action is a chain of waits: find the name, open a connection, encrypt, send the request, do server work, download the response, then render.
Read the diagram definition
sequenceDiagram
autonumber
participant Client
participant DNS
participant Edge as Edge or proxy
participant App as Application tier
participant Data as Cache or database
Client->>DNS: Resolve host, often cached
DNS-->>Client: Address from DNS records or traffic policy
Client->>Edge: TCP 3-way handshake or QUIC setup
Client->>Edge: TLS handshake and protocol negotiation
Client->>Edge: Send headers and optional body
Edge->>App: Reuse pooled upstream connection
App->>Data: Read, write, or cache lookup
Data-->>App: Result
App-->>Edge: Status, headers, body
Edge-->>Client: Compressed response bytes
Client->>Client: Parse, execute, render, cacheSetup
A cold DNS lookup, TCP hello, and TLS check can burn one or more round trips before your app code even runs.
Bytes
Big cookies, fat headers, and large JSON bodies make upload and download slower. Compression and smaller payloads help.
Client world
Browsers add rules and limits. Phones add radio wake-up, loss, and network switches.
Cold start
DNS + TCP + TLS can easily cost a few hundred milliseconds on a 100ms path before useful request bytes finish setup.
Warm connection
A reused HTTPS connection can send immediately and skip most of that setup tax.
Header bloat
Large cookies and auth headers add cost on every request, even before the body starts.
Smaller bodies
If a 50 KB JSON response compresses to 10 KB, transfer size falls by 40 KB. The client still decompresses and parses the original JSON structure, so compression alone does not reduce JSON parsing work. Removing unneeded fields can reduce both transferred data and parsing effort.
Why reusing a connection feels so much faster
Saying hello once and keeping the pipe warm beats paying the hello tax on every request, especially when latency is high or you make many calls.
Without reuse
Twenty separate cold calls can burn seconds just on setup if they cannot overlap well.
With keep-alive
The first call pays setup. The next ones mostly pay for server work and moving the payload.
With multiplexing
Many requests can share one encrypted connection at once, which is how modern pages stay efficient.
Lifecycle and reuse math set the stakes. Day-to-day knobs are TLS, streams, CDN negotiation, and compression.
Operations and compatibility
Caching is a reuse decision, while validation checks whether a stored response remains current. A fresh cache entry can be reused without contacting the origin. An expired entry may be revalidated with its ETag; an unchanged resource can return 304 and avoid retransmitting its body. “no-cache” requires validation before reuse, whereas “no-store” prohibits storing the response. Pick these directives based on the data’s visibility and freshness requirements.
Performance optimization tips
Origin / reverse proxy
- Terminate with TLS 1.2+; prefer TLS 1.3 so the hello spends fewer round trips.
- Enable HTTP/2 on 443 and tune max concurrent streams (often ~100–128; see Numbers to know).
- Use push_preload only when traces show it wins; push can waste bytes on cache misses.
server {
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
http2_max_concurrent_streams 128;
http2_push_preload on;
}CDN and edge
- Turn on HTTP/3 where the vendor supports it; that is often the first hop users hit.
- Set Cache-Control for static assets; separate cache keys for compressed variants.
- Use Early Hints (103) or link preload to overlap RTTs with critical CSS and JS.
- Serve Brotli on compressible text; keep images in modern formats at the edge.
Measure
- Track protocol mix: HTTP/1.1 vs HTTP/2 vs HTTP/3 share by region and client.
- Split TTFB into TLS handshake versus origin wait so you know which layer to fix.
- Watch connection setup latency and QUIC 0-RTT eligibility against replay trade-offs.
- Report cache hit ratio and bytes saved from compression to prove edge wins.
How HTTP stays backward compatible
Every new HTTP version can talk to older servers and clients. That is why the web did not break when HTTP/2 or HTTP/3 launched: billions of devices upgraded at their own pace.
- during TLS lets the server pick the highest version both sides understand.
- GET, POST, headers, and status codes stay the same; only framing and transport change.
- Unknown headers are ignored, so new features stay opt-in without breaking old clients.
- can advertise HTTP/3 after HTTP/2; if UDP is blocked, the client stays on TCP.
Enable HTTP/3 on the CDN edge gradually; old browsers fall back. The same API can serve HTTP/3 phones, HTTP/2 tablets, and HTTP/1.1 IoT without a flag day.
Debugging questions
- Which protocol did the client actually negotiate on this request?
- Is UDP/443 blocked so HTTP/3 users quietly fall back to HTTP/2?
- Are large cookies or auth headers bloating bandwidth on every request?
- Is tail latency due to TLS setup, origin wait, packet loss, or download size?
- Are retries safe for this HTTP method and endpoint semantics?
Compatibility keeps old clients online. Close with on HTTP, the version invariant, a timing story, exercises, and references.
on HTTP
REST is not a protocol. It is an architectural style that maps CRUD operations to HTTP verbs and resources to URLs. It is the default way APIs are built because HTTP already provides everything REST needs.
| HTTP feature | REST uses it for | Example |
|---|---|---|
| URLs | Resource identity | /api/users/123 |
| Verbs | CRUD operations | GET, POST, PUT, DELETE |
| Status codes | Operation results | 200 OK, 404 Not Found |
| Headers | Auth, caching, negotiation | Authorization, Cache-Control |
| Body | Data payload | {"name":"Alice"} |
- No new protocol is needed; existing CDNs, proxies, and caches already understand HTTP.
- A GET URL is a natural cache key, so intermediary caches can serve repeats cheaply.
- Requests stay human-readable in a browser or curl, which speeds debugging.
- Stateless requests carry what they need, so you can scale app servers horizontally.
- Pain points include over-fetching, under-fetching, endpoint explosion, and costly /v1 /v2 versioning.
Those pain points helped motivate GraphQL for some large mobile clients. Resource naming, idempotency, pagination, HATEOAS, and versioning live in the dedicated REST API Design note; this section only places REST inside HTTP's ecosystem.
Correctness invariant
What must never become false
HTTP semantics stay stable across versions: a safe method stays safe, an idempotent method stays idempotent, and intermediaries must not invent authority from client-supplied identity headers. Transport upgrades (, ) may change framing and latency. They must not change what a GET or POST means to the application.
Worked example: cold vs warm connect
Specify what the timing includes. In a simplified loss-free TCP plus TLS 1.3 exchange, connection setup costs roughly two round trips before a normal request; request-to-first-response adds another round trip plus server work. A warm connection avoids setup but still pays that request-response travel. Independent calls can overlap, whereas dependent calls form a serial critical path. Do not add every connection’s setup duration when estimating a parallel page load.
One API call on a new TCP + TLS 1.2 path waits for setup, then one more RTT for the response. Reuse the connection (or use HTTP/3 / 0-RTT where allowed) and setup collapses. Exact floors and the 50K QPS capacity reading are in Numbers to know.
Failure timeline: TCP HOL under HTTP/2
T0 Browser opens one HTTP/2 TCP connection; 12 streams multiplexed T1 One packet for stream #3 is lost on a 2% lossy mobile path T2 TCP holds the connection until retransmission; streams #1–#12 stall T3 Page looks "hung" even though only one image stream needed the packet T4 HTTP/3/QUIC recovers that stream independently; others keep flowing
HTTP/2 fixed HTTP-level head-of-line blocking; TCP loss can still freeze every multiplexed stream until recovery (same idea as the generations section above).
Exercises
Exercise 1
At 80ms RTT and 1% packet loss, argue for HTTP/2 vs HTTP/3 at the CDN edge. What metric would make you reverse the decision?
Exercise 2
2KB of cookies on every API call at 40K QPS. Quantify header bandwidth and propose two concrete reductions without breaking auth.
Exercise 3
A proxy strips Authorization and injects X-User-Id. What must services verify, and what must never become false?
Try it: HTTP version multiplexer on one page load
Load six small assets. Step simulated time one RTT at a time and compare sequential HTTP/1.1 turns with multiplexed HTTP/2 and QUIC-style independence for HTTP/3.
HTML
0/3 RTTs
CSS
0/3 RTTs
JS
0/3 RTTs
font
0/3 RTTs
image
0/3 RTTs
API
0/3 RTTs
- Simulated time
- 0 ms
- Assets complete
- 0/6
- Active version
- HTTP11
HTTP/1.1 typically completes one request per connection turn before starting the next. HTTP/2 multiplexes many streams on one connection.
Topic-specific references
- RFC 9110: HTTP Semantics: methods, status codes, caching rules that versions must preserve.
- RFC 9114: HTTP/3: HTTP over QUIC framing and stream independence.
- RFC 9000: QUIC: connection IDs, loss recovery, and migration.
What to remember
Default to for many web clients and service calls (including gRPC), and enable on CDN/edge for mobile. Biggest wins come from cutting unnecessary round trips: , header compression, and . In interviews, say HTTP/2 solves HTTP-level but not TCP-level blocking, and HTTP/3 addresses both.
Practice and further reading
Use these exercises to connect the lesson to a design decision. State your assumptions, trace what happens to one request, and explain the rule that must remain true even during a failure. That rule is the design's correctness invariant.
Focus for this lesson: Reason about multiplexing, head-of-line blocking, prioritization, compression, connection migration, and replay risk across HTTP versions.
Explain the decision
Why does HTTP/2 multiplexing not eliminate all head-of-line blocking?
Read the reasoning
Streams are independent at HTTP level, but TCP delivers one ordered byte stream, so a lost packet can stall data for every stream on that connection.
Apply it to a production scenario
Enabling HTTP/3 helps mobile p95 but increases CPU. How do you roll it out?
Read one approach
Advertise with Alt-Svc to a cohort, compare handshake/recovery and CPU metrics, retain HTTP/2 fallback, and isolate 0-RTT operations that are replay-safe.
Work through the numbers
At 100 ms RTT, compare a new HTTP/1.1 TLS connection needing about 2 RTT before request data with QUIC 1-RTT setup.
Read the worked calculation
The simplified setup delta is about 100 ms; include DNS, resumption, loss, and server processing separately.
Reason through a failure
What invariant applies to HTTP 0-RTT requests?
Read the recovery reasoning
Replayable early data must never perform a non-idempotent business mutation without an application replay defense.
Sources and further study
Continue with REST API Design.