Course contents

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.

FreeMust know for interviewsLesson16 min readBeginner → SeniorReviewed Oct 2026
On this page
  1. Request and response anatomy
  2. Methods: safe, idempotent, cacheable
  3. Status codes that come up in design interviews
  4. Caching headers: Cache-Control and ETag
  5. HTTP/1.1 vs HTTP/2 vs HTTP/3
  6. HTTPS: what TLS 1.3 costs
  7. Where TLS terminates in a design
  8. Keep-alive and connection reuse
  9. Trade-offs and failure modes
  10. How it shows up in design problems
  11. Follow-up questions
  12. 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 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's If-None-Match: "v7", so it sent the full body with a new ETag.
  • 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-maxage overrides 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.
  • private vs public: only the user's browser may store it, vs CDNs and proxies too.
  • ETag plus If-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.

Fig. 1

A new HTTPS request over TCP and TLS 1.3

A new HTTPS request over TCP and TLS 1.3A sequence diagram between a client and a server. Round trip one is the TCP handshake. Round trip two is the TLS 1.3 handshake: the client sends ClientHello with a key share, the server answers with ServerHello, its certificate and Finished, and everything after ServerHello is encrypted. Round trip three carries the encrypted HTTP request and response. A resumed connection can send the request in the first TLS flight as 0-RTT data, which an attacker can replay.1SYN2SYN-ACK3ACK4ClientHello + key share5ServerHello, certificate, Finishedencrypted from here on6Finished + GET /orders/427200 OK + bodyClientServerTLS 1.2 needed 2 round tripsTLS 1.3 needs 1 on a new connection0-RTT on resumptionrequest in the first flight, but replayableRTT 1: TCPRTT 2: TLSRTT 3: HTTP
A fresh HTTPS request is three round trips; a reused connection is one. TLS 1.3 halved the handshake, and 0-RTT removes it on resumption at the price of replay risk.

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.

In the real world

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.

Fig. 2

Chain of trust: how a browser decides to believe a certificate

Chain of trust: how a browser decides to believe a certificateThree certificates left to right. A root CA certificate lives in the operating system or browser trust store and signs an intermediate CA certificate. The intermediate signs the site certificate for api.example.com. The server sends the site certificate and the intermediate; the client checks each signature up to a root it already trusts, plus the name, the dates and revocation.The server sends these twosignssignsRoot CAself-signed, key kept offline;shipped in the OS / browsertrust storeIntermediate CAsigned by the root;signs site certificatesday to dayapi.example.comsigned by the intermediate;holds the server's public keyand its namessite certificate + intermediate (not the root)The client checkseach signature up to aroot it already trustsname matches the hostnot expired, not revokedMissing intermediate = "works in my browser, fails in the Java client".
Trust flows from a root the client already has. The server must send its intermediate; leaving it out is a classic outage for non-browser clients.

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.

Fig. 3

Where TLS terminates in a typical design

Where TLS terminates in a typical designA request path from left to right: client, CDN edge, load balancer, service, database. TLS from the client ends at the CDN edge near the user. The CDN opens its own TLS connection to the load balancer. Behind the load balancer, traffic to services is either re-encrypted with mutual TLS or plain HTTP on a private network, and the service talks TLS to the database.ClientCDN edgenear the userLoad balanceror reverse proxyServiceDatabaseTerminate at the edgeshort RTT handshake near the usercache, WAF, compression needto see plaintextcertificates live in few placesRe-encrypt insidezero-trust networks; securityor compliance policy may require itmTLS also proves whichservice is callingPass through (L4)LB forwards encrypted bytesservice holds the private keyno L7 routing, no cachingend-to-end with the clientTLS #1TLS #2mTLS or plainTLS
Each TLS hop is a separate connection. Terminating at the edge buys latency and L7 features; re-encrypting inside buys defence in depth.

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.

Where candidates slip

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+.

Performance angle

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: POST with 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-After and 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)