Networking8 min · 30 of 64

HTTP/HTTPS

Choose HTTP methods and status codes you can defend, price a cold TLS handshake in round trips, and set cache headers that will not leak a private page.

HTTP is a stateless request/response protocol for moving resource representations between clients and servers. Statelessness is why any request can go to any machine behind a load balancer, and why every guarantee you want must travel inside the request. What changes an architecture is which methods are safe to retry, which status codes are worth retrying, how caching is negotiated, and what connection reuse saves.

Keep-alive removes two round trips of handshake from every request after the first.
HTTP without and with connection reuseALT[new connection per request · 3 requests, 3 handshakes][keep-alive · one connection, many requests]TCP + TLS handshake · 2 RTTGET /a200 · Connection: closeTCP + TLS handshake again · 2 RTTGET /b200 · Connection: closeTCP + TLS handshake · onceGET /a200 · keep-aliveGET /b · no handshake200GET /c · HTTP/2 can send this in parallel200ClientServer

Scroll to zoom · drag to pan · 0 fits · Esc closes

The model

A request is a method, a URI, a version, headers and an optional body; a response swaps the method and URI for a status code. Any state the server needs — session, cart, identity — rides in a cookie or an Authorization header, which is why a load balancer can send your next request elsewhere (stateful vs stateless). HTTP/1.1 and HTTP/2 run over TCP, HTTP/3 over QUIC, so reliable delivery lives below HTTP. Resource-shaped URIs rather than action-shaped ones are REST.

Methods and idempotency

  • GET — retrieve. Safe and idempotent.
  • POST — submit or create. Not idempotent.
  • PUT — replace the whole resource. Idempotent.
  • DELETE — remove it. Idempotent.
  • PATCH — partial change; idempotent only if the patch operation is (status = paid is, balance += 10 is not).
  • HEAD / OPTIONS — headers only, and allowed methods. Safe and idempotent.

Idempotent means N identical requests leave the same state as one, and that is the line with architectural weight. A client that times out has learned nothing about whether the server processed the request: the timeout is a coin flip between "lost on the way out" and "applied, lost on the way back". Retrying is therefore at-least-once delivery, at-least-once produces duplicates, and duplicates mean the write path needs idempotency: an Idempotency-Key stored against the result and replayed, or a client-chosen id that turns the create into a PUT.

Status codes are a retry policy

  • 2xx200 OK, 201 Created (URI in Location), 204 No Content.
  • 3xx301 permanent, 302 temporary, 304 Not Modified (validation, below).
  • 4xx — the request cannot be served as sent: 400 malformed, 401 unauthenticated, 403 not permitted, 404 missing, 408 Request Timeout, 429 Too Many Requests.
  • 5xx — the server failed a valid request: 500 generic, 503 Service Unavailable.

Retry safety does not split along the 4xx/5xx line, and "never retry a 4xx" is the version interviewers catch. What decides it is whether the response calls the request defective or the server's condition temporary. 400, 401, 403 and 404 fail identically on every attempt, so retrying them burns budget for nothing. But 429 is a 4xx, and it is the server asking you back, usually with Retry-After saying when; 408 and 425 Too Early are the same shape. Retry 429, 408, 425 and 503 with backoff and jitter, never the other four, and treat a bare 500 as ambiguous: the work may have committed before it failed, which puts you back on idempotency.

HTTPS and what the handshake costs

HTTPS is HTTP over TLS: encryption so the path cannot read the traffic, authentication so a certificate chain proves you reached the real origin, integrity so nothing was altered in flight. Chrome and Firefox have marked plain HTTP "Not secure" since 2018, and the certificate mechanics are in encryption. The bill is one round trip for TCP plus one for TLS 1.3 (two for TLS 1.2), priced against the shared ladder: 0.5 ms inside a datacenter, roughly 200 ms India to US East.

Cold, cross-continent: 200 (TCP) + 200 (TLS 1.3) + 200 (request/response) = ~600 ms
Warm, same connection:                             200                     = ~200 ms

A Mumbai client calling a US East origin spends two thirds of its first request on setup. Twenty sequential calls with Connection: close cost 20 × 600 ms = 12 s; keep-alive pays the handshake once, so the same twenty cost 400 ms + 20 × 200 ms = 4.4 s, and HTTP/2 overlaps them into roughly one round trip: about 600 ms.

Pool sizing follows from Little's Law: concurrency = arrival rate × latency. A gateway sending 500 QPS to an upstream answering in 200 ms at p99 needs 100 requests in flight; a pool of 20 turns over 5 requests per connection per second, capping completed work at 100 QPS against 500 offered. The upstream is busy on all 20 slots rather than idle, just never handed more than a fifth of the load, and the backlog is not a stable 80: it grows by ~400 requests per second until timeouts or a bounded queue shed them. The rejected fix is a longer client timeout, which turns a fast failure into a slow one.

Versions

HTTP/1.1 made connections persistent, which is what makes the keep-alive arithmetic above possible: one handshake per connection instead of one per request. HTTP/2 added multiplexing and HPACK header compression, worth real bytes when a 700-byte cookie repeats on all twenty requests, and it removed head-of-line blocking at the application layer but not at the transport layer — TCP delivers bytes in order, so one lost segment stalls every stream until the retransmission lands. HTTP/3 over QUIC recovers loss per stream instead, a win on lossy mobile links and near nothing on a clean datacenter one. Server push is dead: Chrome dropped it in 2022 and Firefox followed, and 103 Early Hints replaced it.

Caching is negotiated, not assumed

Two mechanisms hide under "HTTP caching" and they cost different amounts. Cache-Control: max-age=N is a promise: for N seconds the client serves its copy with no network at all. Validation is the other — an ETag on the response, If-None-Match on the next request, 304 Not Modified with no body. A 304 still pays the full round trip, so its time to first byte matches a 200 exactly and the only saving is transfer: nothing measurable on 2 KB of JSON, seconds on a 300 KB bundle over a slow link. Only max-age removes the network.

The failure mode is a cache header that ignores identity. Mark a per-user response Cache-Control: public, max-age=300 and any shared cache in the path — a CDN node, a corporate proxy — may hand user A's dashboard to user B for five minutes. Session-derived responses are private at minimum, no-store when they must never touch a disk, Vary: Authorization when a shared cache keys per credential — the same rules that bound your cache tier.

In an interview

The flow, the methods and their idempotency, the status families and what HTTPS provides are table stakes and take thirty seconds. What is tested is whether you then settle decisions out loud with them. Retry safety: "the client retries 503, 429 and 408 with backoff and jitter, but POST /orders takes an Idempotency-Key because a timeout can't tell us whether the charge landed." Latency: "clients are 200 ms away, so TCP plus TLS 1.3 is 400 ms before the first byte, 200 ms of it TLS — terminate TLS at the edge and keep connections alive." Caching: "the bundle is content-hashed, so max-age=31536000, immutable; the dashboard is per-user, so private, no-store."

The mistake that loses points is retrying a timed-out POST as if a timeout meant failure — it is the ambiguity, not the error, and "we'll just retry" on a non-idempotent write says you have never debugged a double charge. Second is claiming HTTP/2 eliminates head-of-line blocking; third is reaching for HTTP/3, or a server push browsers no longer implement, when the connection is already warm and the real cost is a database query.

Check yourself

1. A 300 KB JS bundle is served 2 million times a day over a 30 ms link. Price no-cache with an ETag against max-age=31536000 on a content-hashed filename. Which ships?

2M ÷ 86,400 ≈ 23 QPS, and 300 KB × 2M ≈ 600 GB/day of egress if every hit ships the body. Revalidation still pays a 30 ms round trip on all 23 QPS for a 304: egress falls to headers, latency does not move. The hashed filename with a one-year max-age serves from disk: zero round trips, zero origin QPS, the same 600 GB/day avoided, invalidated by renaming. Ship it, and revalidate only the un-hashed HTML entry point.

2. Your p99 budget for an API call is 200 ms and mobile clients sit roughly 200 ms away. Can you meet it on a cold connection, and what do you change?

No. TCP plus TLS 1.3 is 2 × 200 = 400 ms before the request is even sent, so the floor is ~600 ms against a 200 ms budget and no application change moves it. Terminate TLS at an edge PoP 10–30 ms from the user: the handshake costs ~60 ms, only a cache miss crosses the ocean, and keeping the connection alive drops later calls to one round trip.

3. A gateway sends 500 QPS to an upstream whose p99 is 200 ms, through a pool of 20 keep-alive connections. What breaks, and what is the smallest correct change?

The load demands 500 × 0.2 = 100 in flight, but 20 connections at 200 ms each complete only 20 / 0.2 = 100 QPS. The pool is the ceiling, not the upstream: it passes a fifth of the offered load, the backlog grows ~400 per second until timeouts shed it, and the upstream stays busy but under-fed. Size the pool past 100 with headroom. A longer timeout only turns a fast failure into a slow one, and if the upstream cannot go faster, shed load rather than queue it.