HTTP and HTTPS
HTTP and HTTPS for system design interviews: request and response anatomy, safe and idempotent methods, the status codes that matter, Cache-Control and ETag, HTTP/1.1 vs HTTP/2 vs HTTP/3, the TLS 1.3 handshake and 0-RTT replay risk, certificate chains, where TLS terminates, and connection reuse with Spring RestClient timeouts.
On this page
- Request and response anatomy
- Methods: safe, idempotent, cacheable
- Status codes that come up in design interviews
- Caching headers: Cache-Control and ETag
- HTTP/1.1 vs HTTP/2 vs HTTP/3
- HTTPS: what TLS 1.3 costs
- Where TLS terminates in a design
- Keep-alive and connection reuse
- Trade-offs and failure modes
- How it shows up in design problems
- Follow-up questions
- How to say it in the interview
🌐 What are HTTP and HTTPS?
HTTP is the request/response protocol almost every API and web page speaks: a client sends a method, a path and headers, and the server answers with a status code, headers and usually a body. HTTPS is the same HTTP inside a TLS connection, which encrypts the traffic and proves the server is who it claims to be. Think of HTTP as a letter written on a postcard and HTTPS as the same letter in a sealed envelope, delivered by a courier who checked the recipient's ID.
For this lesson, we'll cover request anatomy, the method and status code semantics interviewers probe, caching headers, HTTP/1.1 vs 2 vs 3, what TLS 1.3 costs, certificates, and where TLS ends in a design. Every diagram you draw has arrows labelled "HTTPS", and the follow-ups ("is that retry safe?", "which status code?", "where's TLS terminated?") separate people who've run services from people who've only called them.
Below the line (out of scope)
- The transport underneath: TCP vs UDP
- Designing resources, URLs and error bodies: API design for system design interviews
- Proxies and TLS offload in depth: Proxy vs reverse proxy
- Logins, tokens and mutual TLS: Authentication, Service-to-service auth and mTLS
The 30-second answer
HTTP is stateless request/response: a method, a target, headers and an optional body, answered by a status code, headers and a body. Methods carry promises that matter for retries: GET, HEAD and OPTIONS are safe, and those plus PUT and DELETE are idempotent, while POST and PATCH aren't. HTTP/1.1 sends one request at a time per connection, HTTP/2 multiplexes many streams over one TCP connection but still suffers TCP head-of-line blocking, and HTTP/3 runs over QUIC on UDP to remove it. HTTPS adds TLS: TLS 1.3 needs one round trip on a new connection, on top of TCP's one, and 0-RTT resumption saves that trip but can be replayed, so only idempotent requests should use it. In a design I terminate TLS at the edge (CDN or load balancer), re-encrypt or use mTLS inside if policy needs it, and reuse connections so most requests skip the handshakes entirely.
What you’ll learn
- Read a raw HTTP request and response and say what each header is for
- Use the safe and idempotent method properties to decide what a client may retry
- Pick the right status code for creates, async work, conflicts, validation, rate limits and gateway errors
- Set Cache-Control and ETag so browsers and CDNs absorb repeat reads
- Compare HTTP/1.1, HTTP/2 and HTTP/3 on multiplexing, head-of-line blocking, header compression and transport
- Cost the TLS 1.3 handshake in round trips, explain 0-RTT replay risk, and say where TLS terminates in a design
01Request and response anatomy
Every later topic is a header or a status code on this shape. Here's a conditional GET and its answer, written the HTTP/1.1 way (HTTP/2 and HTTP/3 carry the same fields in binary frames).
GET /v1/orders/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Accept: application/json
If-None-Match: "v7"
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=60
ETag: "v8"
Content-Length: 87
{"id": 42, "status": "SHIPPED", "total": 1299, "currency": "INR", "updatedAt": "2026-10-20T09:12:00Z"}
Notice three things:
- The first line is the verb and the target: method, path, version. The response's first line is the status.
- Headers carry the metadata: who you are (
Authorization), what you accept, and caching hints. The server didn't match the client'sIf-None-Match: "v7", so it sent the full body with a newETag. - HTTP is stateless. Each request carries everything the server needs. Sessions, logins and carts are built on top with cookies or tokens, which is what lets you put any server behind a load balancer.
02Methods: safe, idempotent, cacheable
RFC 9110 gives every method properties, and these are what a retrying client, a proxy and a cache rely on. Safe means the client isn't asking for any state change. Idempotent means sending it twice has the same intended effect as sending it once.
| Method | Typical use | Safe | Idempotent | Cacheable |
|---|---|---|---|---|
GET |
Read a resource | Yes | Yes | Yes |
HEAD |
Headers only (size, ETag) | Yes | Yes | Yes |
OPTIONS |
Capabilities, CORS preflight | Yes | Yes | No |
PUT |
Create or replace at a known URL | No | Yes | No |
DELETE |
Remove | No | Yes | No |
POST |
Create, or trigger an action | No | No | Rarely |
PATCH (RFC 5789) |
Partial update | No | No (not guaranteed) | No |
The column that matters in design interviews is Idempotent. RFC 9110 lets a client or proxy automatically retry an idempotent request after a dropped connection, and says a proxy must not automatically retry a non-idempotent one. So when a payment POST times out, nobody can safely resend it unless you add an idempotency key. The API design article goes deeper on choosing methods for resources.
03Status codes that come up in design interviews
You need these, and a reason for each.
| Code | Meaning | When I'd use it in a design |
|---|---|---|
| 200 OK | Success with a body | Reads, and updates that return the new state |
| 201 Created | New resource; Location header points at it |
POST /orders |
| 202 Accepted | Queued, not done yet | Video transcode, report export: return a job URL to poll |
| 204 No Content | Success, no body | DELETE, or a PUT that returns nothing |
| 301 / 302 | Moved permanently / temporarily | URL shortener redirects; 301 gets cached by browsers |
| 304 Not Modified | Your cached copy is still good | Conditional GET with If-None-Match |
| 400 Bad Request | Malformed request | Unparseable JSON, missing required field |
| 401 Unauthorized | Not authenticated (despite the name) | Missing or expired token |
| 403 Forbidden | Authenticated, but not allowed | User reading someone else's order |
| 404 Not Found | No such resource | Unknown ID (also used to hide existence) |
| 409 Conflict | Clashes with current state | Version conflict, seat already booked |
| 422 Unprocessable Content | Well-formed but invalid | Quantity is negative, date in the past |
| 429 Too Many Requests | Rate limited (RFC 6585) | Send Retry-After; see rate limiting |
| 500 Internal Server Error | Bug or unhandled failure on this server | Never on purpose |
| 502 Bad Gateway | A proxy got a bad or no response from upstream | The app crashed or closed the connection behind the LB |
| 503 Service Unavailable | Overloaded or down for now | Load shedding, maintenance; add Retry-After |
| 504 Gateway Timeout | A proxy gave up waiting for upstream | Upstream slower than the proxy's timeout |
The split I want to hear: 4xx means "don't retry the same request", 5xx and 429 may be worth retrying with backoff, and 502/503/504 usually come from the proxy in front of your service. Retry policy lives in fault tolerance.
04Caching headers: Cache-Control and ETag
HTTP has caching built in (RFC 9111), and in a read-heavy design these headers are free capacity.
Cache-Control: max-age=N: fresh for N seconds; reuse without asking.s-maxageoverrides it for shared caches like a CDN.no-cache: you may store it, but must revalidate before each use. It does not mean "don't cache".no-store: never store it. Use for secrets and personal data.privatevspublic: only the user's browser may store it, vs CDNs and proxies too.ETagplusIf-None-Match: a version tag. If it still matches, the server answers 304 with no body, which saves bandwidth but still costs a round trip.
My defaults: hashed static assets get public, max-age=31536000, immutable (a year), a product page gets a short s-maxage, a user's order gets private with an ETag. ETags also power optimistic locking with If-Match, covered in the API design article; the CDN side is in CDN.
05HTTP/1.1 vs HTTP/2 vs HTTP/3
Same methods, headers and status codes in all three; what changes is how bytes are carried.
| HTTP/1.1 (RFC 9112) | HTTP/2 (RFC 9113) | HTTP/3 (RFC 9114) | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| Format | Text | Binary frames | Binary frames |
| Requests per connection | One at a time (browsers open up to ~6 connections per host) | Many concurrent streams on one connection | Many concurrent streams |
| Head-of-line blocking | Per connection: a slow response blocks the next | Fixed at the HTTP layer, but one lost TCP packet stalls all streams | Gone across streams: loss only stalls its own stream |
| Header compression | None | HPACK (RFC 7541) | QPACK (RFC 9204) |
| TLS | Optional | Effectively required by browsers | Built in (TLS 1.3) |
| New connection setup | TCP + TLS: 2 RTT with TLS 1.3 | Same | 1 RTT, 0 on resumption |
Header compression matters because an API call can carry a kilobyte of cookies and tokens; HPACK and QPACK send repeats as small table references. HTTP/2 is the default at the edge and for gRPC; HTTP/3 pays off on mobile and lossy networks (TCP vs UDP explains why).
06HTTPS: what TLS 1.3 costs
TLS gives you three things: encryption (nobody on the path can read it), integrity (nobody can change it unnoticed) and authentication (the certificate proves the server's identity). The cost is a handshake, and the figure shows exactly where it sits.
A new HTTPS request over TCP and TLS 1.3
TCP's handshake is the first round trip. In the second, the client sends its ciphers and a key share, and the server replies with its key share, certificate and Finished; both sides now have keys, and the client's Finished travels with the first request. TLS 1.2 needed two round trips for this.
0-RTT is the shortcut for a client that has talked to this server before: it uses a pre-shared key from the last session to encrypt the request in its very first flight. RFC 8446 is explicit that this early data isn't forward secret and has no replay protection across connections. An attacker who captures it can send it again, so 0-RTT must only carry requests that are safe to repeat.
When Cloudflare introduced TLS 1.3 0-RTT for its customers in March 2017, it took a deliberately conservative line on replay: it only answered GET requests without query parameters over 0-RTT, capped the size of early data and how long it could be replayed, and added a header telling the origin that a request arrived as early data. Their reasoning was that applications should be replay-safe but often aren't. That's the design lesson for an interview: 0-RTT is a latency win for idempotent reads, and a liability for anything that moves money. RFC 8470 later standardised the signals (Early-Data: 1 and status 425 Too Early).
Certificates and the chain of trust
A certificate binds a public key to names like api.example.com, and it's signed by a certificate authority. The client doesn't know your certificate, but it does trust a small set of root CAs shipped with the OS or browser, so it walks the chain.
Chain of trust: how a browser decides to believe a certificate
Roots stay offline and sign intermediates (Let's Encrypt issues subscriber certificates only from intermediates). If the server forgets the intermediate, browsers often paper over it with cached copies while a Java service fails the handshake.
07Where TLS terminates in a design
"Terminating TLS" means the hop where traffic is decrypted. It decides who sees plaintext, where certificates live and what can be cached.
Where TLS terminates in a typical design
My default: terminate at the CDN or load balancer close to users, so the handshake round trips are short and the edge can cache, compress and route on paths. Inside, re-encrypt with mTLS when the network isn't trusted or a security policy demands it; a service mesh makes that cheap to operate. Pass-through (an L4 balancer forwarding encrypted bytes) is for when the backend must hold the key, and you give up path routing and caching. The proxy side is covered in Proxy vs reverse proxy.
Many candidates draw "HTTPS" on the client arrow and stop. Then the interviewer asks how the load balancer routes on /api/v2/... if it can't read the request, or whether traffic behind it is encrypted, and there's no answer. Say where TLS ends, what's encrypted after it, and why. One sentence is enough: "TLS terminates at the load balancer; service-to-service traffic uses mTLS from the mesh."
08Keep-alive and connection reuse
HTTP/1.1 connections are persistent by default, and HTTP/2 and HTTP/3 multiplex many requests on one connection. The point of both is to pay the TCP and TLS handshakes once and then send requests at one round trip each.
Try it yourself first
Before reading on, work this out. Your API server calls an inventory service in another region, 80 ms round trip, 50 times a second. If every call opens a new HTTPS connection (TLS 1.3), how much time per call is pure setup, and how many TLS handshakes per minute does the inventory service burn? What changes with a pool of 10 keep-alive connections?
My rough arithmetic: a new connection costs 2 RTT of setup (160 ms) before the 80 ms request, so two thirds of each 240 ms call is handshake, and the inventory service does 3,000 full handshakes a minute. With a pool, handshakes happen once per connection lifetime and calls drop to about 80 ms. In Spring, that's a client built once, with explicit timeouts:
@Bean
RestClient inventoryClient() {
HttpClient jdk = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(2)) // TCP + TLS setup must finish in 2 s
.version(HttpClient.Version.HTTP_2) // falls back to HTTP/1.1 if the server can't
.build(); // the JDK client pools connections itself
var factory = new JdkClientHttpRequestFactory(jdk);
factory.setReadTimeout(Duration.ofSeconds(3)); // waiting for the response
return RestClient.builder()
.baseUrl("https://inventory.internal")
.requestFactory(factory)
.build();
}
Notice the bean is a singleton: a new client per request throws the pool away. The two timeouts differ on purpose: a slow connect usually means the host is down, a slow read means it's busy. This needs Spring Framework 6.1+.
Keep-alive has edges. Servers and load balancers close idle connections, and a client reusing one they just closed fails on the first write, so keep the client's idle timeout shorter than the server's and retry idempotent requests once on a reset. Cap connection lifetime too: a pool that never recycles never notices new backends behind DNS.
09Trade-offs and failure modes
| What breaks | Why | What to do |
|---|---|---|
| Duplicate orders after a timeout | Client retried a non-idempotent POST |
Idempotency keys; only auto-retry idempotent methods |
| Burst of 502s during deploys | LB sends to an instance that's shutting down, or reuses a closed keep-alive connection | Graceful drain, health checks, idle timeouts aligned |
| 504s while the app looks healthy | Proxy timeout shorter than a slow endpoint | Make long work async with 202, or align timeouts per route |
| Handshake failures from some clients | Missing intermediate or expired certificate | Serve the full chain, automate renewal, monitor expiry |
| HTTP/2 server overwhelmed by streams | Clients open and cancel streams faster than the server cleans up (Rapid Reset, 2023) | Stream limits; patched servers |
10How it shows up in design problems
- Design a URL shortener: 301 vs 302 is a real decision. Browsers cache a 301, which saves load but means repeat clicks may never reach you to be counted; a 302 keeps every click visible at the cost of more traffic.
- Design a payment system:
POSTwith an idempotency key, 409 for conflicting state, 202 for asynchronous settlement, TLS everywhere, no 0-RTT on writes. - Design a rate limiter: 429 plus
Retry-Afterand rate-limit headers are the client-facing contract. - Design an LLM gateway: long-running streaming responses mean read timeouts per route, not one global value, and connection pools to each upstream provider.
11Follow-up questions
What if a mobile client on a flaky network sends a POST and the connection drops before the response?
The client can't know whether the server processed it. With HTTP/1.1 or HTTP/2 over TCP, the safe answer is an idempotency key in a header so a retry returns the original result instead of creating a second order. HTTP/3 helps the network part (connection migration, fewer stalls), not the "did it happen?" part.
What if we moved everything to HTTP/2: does head-of-line blocking go away?
At the HTTP layer, yes: one slow response no longer blocks the next on the same connection. At the TCP layer, no: all streams share one byte stream, so one lost packet stalls them all, and on lossy links HTTP/2 can do worse than several HTTP/1.1 connections. Removing that is what HTTP/3 is for.
What if security asks for end-to-end encryption, but we need the load balancer to route on paths?
Then the load balancer must decrypt: terminate TLS there and open a new TLS (ideally mTLS) connection to the backend. Every hop is encrypted; only the load balancer and the service see plaintext. True pass-through means accepting L4 routing.
What if a certificate expires at 2 a.m.?
Every validating client fails the handshake, so it's a full outage for that hostname, and no cache helps because the handshake comes before any request. Automate renewal (ACME) well before expiry and alert on days remaining.
12How to say it in the interview
When I ask about HTTP in a design, I'm listening for three things: that your methods and status codes tell a retrying client what's safe, that you know what a new HTTPS connection costs and avoid paying it per request, and that you can say where TLS starts and stops.
In the interview
"Clients talk HTTPS to our edge. I'd use GET for reads with Cache-Control and ETags so the CDN and browsers absorb repeat traffic, and POST for creating orders with an idempotency key, because POST isn't idempotent and a mobile retry would otherwise double-charge. Responses use 201 with a Location for creates, 202 plus a job URL for slow work, 409 for state conflicts and 429 with Retry-After when we rate limit; clients retry 429 and 503 with backoff but never a 4xx validation error. TLS 1.3 terminates at the CDN and load balancer so the handshake round trip is short and we can route on paths; inside, service-to-service calls use mTLS from the mesh. We serve HTTP/2 to browsers and HTTP/3 where mobile users benefit, and we only accept 0-RTT for idempotent GETs because early data can be replayed. Every internal client is a singleton with a connection pool and explicit connect and read timeouts, because a fresh HTTPS connection costs two extra round trips before the request."
On the whiteboard
- Request: method + target + headers + body → status + headers + body · stateless
- Safe: GET HEAD OPTIONS · Idempotent: + PUT DELETE · not: POST PATCH → idempotency key
- 201 Location · 202 async · 304 cache hit · 401 who? · 403 no · 409 conflict · 422 invalid · 429 Retry-After
- 502 bad upstream · 503 overloaded · 504 upstream timeout · retry 429/5xx, not 4xx
- Cache-Control max-age / s-maxage / no-cache (revalidate) / no-store · ETag + If-None-Match → 304
- HTTP/1.1 one at a time · HTTP/2 streams on TCP (TCP HOL) · HTTP/3 on QUIC (no cross-stream HOL)
- TLS 1.3: 1 RTT (+1 TCP) · 0-RTT on resume = replayable → GET only
- Chain: root (trust store) → intermediate → leaf; serve the intermediate
- Terminate TLS at edge/LB · mTLS inside · pool connections, set connect + read timeouts
Further reading (6)
- RFC 9110: HTTP Semantics: The definitive list of methods, their safe and idempotent properties, retry rules for proxies, and every status code.
- RFC 9111: HTTP Caching: What max-age, s-maxage, no-cache, no-store and validation with ETags actually mean.
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3: The 1-RTT handshake, 0-RTT early data and the spec's own warning that early data can be replayed.
- RFC 9114: HTTP/3: How HTTP maps onto QUIC streams and how clients discover HTTP/3 through Alt-Svc.
- An overview of HTTP (MDN Web Docs): A clear, current walkthrough of messages, statelessness, connections and proxies.
- Introducing Zero Round Trip Time Resumption (0-RTT) (Cloudflare Blog): A production rollout of 0-RTT and the conservative rules used to limit replay.