HTTP/HTTPS deeply
Methods + their safe/idempotent semantics, status-code families, the headers that matter, and what TLS adds.
HTTP is a stateless request/response protocol, and almost every backend decision you make rides on two of its properties: the semantics of each method (safe? idempotent?) and the meaning of each status family. Get those right and retries, caching, and error handling mostly fall out for free. The whole protocol is just text on a wire — a method, a path, headers, a blank line, an optional body — and learning to read that exchange fluently is the single most leveraged skill in backend work.
Methods, safety, and idempotency
The interview answer in one breath: GET, PUT, DELETE are idempotent; POST is not; GET/HEAD/OPTIONS are also safe. Two properties, easy to conflate:
- Safe means read-only — no observable state change on the server. Safe methods are always idempotent (doing nothing N times is still nothing), but the reverse isn’t true.
- Idempotent means repeatable — calling it once or a hundred times leaves the server in the same final state. The response you get back can differ (the first
DELETE /users/42returns204, the second returns404), but the state is identical: the user is gone either way.
Idempotency is the property that lets a client — or a load balancer, or a flaky network, or a retry library — retry without fear. A dropped response to PUT /users/42 is harmless to repeat; you don’t know if the write landed, but repeating it can only converge on the same row. A dropped response to POST /payments is the nightmare case: repeat it and you might charge the card twice. That asymmetry is why payment and order APIs bolt on idempotency keys (covered in 2.2) — they manufacture idempotency for an operation that doesn’t have it natively.
| Method | Safe? | Idempotent? | Typical use | Watch out |
|---|---|---|---|---|
GET | yes | yes | read a resource | never let a GET mutate — caches & crawlers replay it |
HEAD | yes | yes | headers only, no body | for existence/size checks (e.g. Content-Length) |
POST | no | no | create / non-idempotent action | retries can duplicate — add an idempotency key |
PUT | no | yes | replace whole resource | you must send the full representation |
PATCH | no | not guaranteed | partial update | ‘increment by 1’ is non-idempotent |
DELETE | no | yes | remove a resource | 2nd call returning 404 is fine — state is unchanged |
Status families and the headers that matter
Read the first digit first, then the exact code. The first digit answers two questions at once: whose fault is it and could a retry ever help.
| Family | Means | Key codes | Gotcha |
|---|---|---|---|
2xx | success | 200 OK · 201 Created · 204 No Content | 201 should set a Location header to the new resource |
3xx | redirect / cache | 301 moved · 302 found · 304 Not Modified | 304 means ‘use your cache’ — it has no body |
4xx | client error | 400 · 401 · 403 · 404 · 409 · 422 · 429 | 401 = not authenticated; 403 = authenticated but forbidden |
5xx | server error | 500 · 502 · 503 | 502/503 from a gateway/overload are safe-ish to retry with backoff |
A few codes carry outsized weight in interviews. 409 Conflict is the right answer for a write that collides with the current state (a duplicate unique key, a stale optimistic-lock token). 422 Unprocessable Content means the request was well-formed but semantically invalid (validation failed) — distinct from 400, which is for syntactically broken requests. 429 Too Many Requests is the rate-limit signal and should carry a Retry-After header so the client knows when to come back.
The headers worth knowing cold cluster into four jobs:
- Identity & auth —
Authorization: Bearer <token>carries credentials; the server repliesWWW-Authenticateon a401to say how to authenticate. - Content negotiation —
Content-Typedeclares the body’s media type;Acceptlets the client ask for one. Version-by-content-negotiation rides on these. - Caching & concurrency —
Cache-Controlsets freshness rules;ETagis a content fingerprint the client echoes back asIf-None-Match(for caching) orIf-Match(for optimistic concurrency). - CORS — a browser security mechanism. A cross-origin request triggers a preflight
OPTIONSthat the server must answer withAccess-Control-Allow-Origin/-Methods/-Headers, or the browser blocks the real request. CORS is enforced by the browser, not the server —curlignores it entirely.
A request and response, byte by byte
PUT /api/v1/users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
If-Match: "a1b2c3"
{ "name": "Ada", "email": "ada@example.com" }HTTP/1.1 200 OK
Content-Type: application/json
ETag: "d4e5f6"
Cache-Control: no-cache
{ "id": 42, "name": "Ada", "email": "ada@example.com" }Send this request three times and the user ends up identical — that is idempotency. The ETag/If-Match pair adds optimistic concurrency: the server compares the If-Match value against the resource’s current ETag and rejects the write with 412 Precondition Failed if someone else changed the row first. This is the read-then-write race solved at the HTTP layer — no locks, just a version check the client opted into.
The flip side of ETag is caching. The client stored a previous response’s ETag and offers it back; if nothing changed, the server skips the body entirely:
GET /api/v1/users/42 HTTP/1.1
If-None-Match: "d4e5f6"HTTP/1.1 304 Not Modified
ETag: "d4e5f6"A 304 has no body — it’s a few dozen bytes saying your copy is still good. On a large response this turns a multi-kilobyte transfer into almost nothing, which is exactly why CDNs and browsers lean on it so heavily.
What HTTPS adds, and the handshake
HTTPS is just HTTP carried inside a TLS tunnel — the request/response format above is byte-for-byte identical; it’s simply encrypted in transit. TLS buys you three things: encryption (eavesdroppers see ciphertext), integrity (tampering is detected), and server identity (the certificate, signed by a CA your machine trusts, proves you’re really talking to api.example.com and not an impostor).
The rough handshake (TLS 1.3): the client says hello with the cipher suites it supports; the server replies with its certificate and its half of a key exchange; both sides derive the same shared session key from that exchange (using ephemeral Diffie-Hellman, so even a future leak of the server’s private key can’t decrypt old traffic — forward secrecy); from then on every byte is symmetrically encrypted with that session key, which is fast. The expensive asymmetric crypto happens once, up front, just to agree the cheap symmetric key.
01 Learning objectives
0 / 5 done02 Curated reading
03 Knowledge check
- 01easy
Which HTTP methods are idempotent?
- 02easy
Which status family means “the server failed”?
- 03easy
TLS/HTTPS provides encryption, integrity, and server identity.
- 04medium
A request is well-formed but fails business validation. Best status code?
04 Interview questions
browse all ↗What gets asked on this topic — tap a card for how to approach it, the follow-ups, and the trap. Company tags are best-effort & sourced.
-
Which HTTP methods are idempotent, and why does it matter?
GET, PUT, DELETE, HEAD, and OPTIONS are idempotent: making the same call N times leaves the server in the same state as making it once. POST is not idempotent — two POSTs typically create two resources.
It matters for safe retries. When a client times out it cannot tell whether the request was processed, so it must retry. Idempotent methods can be retried freely; for non-idempotent POSTs you need an idempotency key so the server can dedupe.
Follow-ups they push on- How is idempotent different from safe?
- How would you make a payment POST safely retryable?
Red flag Conflating idempotent with safe. GET/HEAD/OPTIONS are also safe (no side effects); PUT/DELETE are idempotent but NOT safe. Also: PUT is idempotent by spec even though it changes data.
source: MDN — HTTP request methods ↗ -
Walk me through the HTTP status code families and name a key code in each.
Five families by first digit: 1xx informational, 2xx success (200 OK, 201 Created, 204 No Content), 3xx redirection (301 permanent, 302 found, 304 Not Modified), 4xx client error (400 bad request, 401 unauthenticated, 403 forbidden, 404 not found, 409 conflict, 422 unprocessable, 429 too many requests), 5xx server error (500 internal, 502 bad gateway, 503 unavailable).
The useful instinct: 4xx means the client must change the request; 5xx means the client can retry the same request later.
Follow-ups they push on- 401 vs 403 — what is the difference?
- When would you return 422 instead of 400?
- What does 304 require the client to have sent?
Red flag Returning 200 with an error body, or using 401 when you mean 403. 401 = not authenticated (who are you?), 403 = authenticated but not allowed.
source: MDN — HTTP response status codes ↗ -
What is an ETag and how does conditional caching with If-None-Match work?
An
ETagis an opaque validator (often a hash) the server attaches to a response to identify a specific version of a resource. On the next request the client sendsIf-None-Match: <etag>. If the resource is unchanged the server replies 304 Not Modified with no body, saving bandwidth; if it changed it returns 200 with the new body and a new ETag.ETags also enable optimistic concurrency on writes via
If-Match: the write is rejected with 412 Precondition Failed if someone else changed the resource first.Follow-ups they push on- Strong vs weak ETags?
- How does this compare to Last-Modified / If-Modified-Since?
Red flag Thinking 304 carries the body — it does not; the client reuses its cached copy. Also forgetting ETags can prevent lost-update races on PUT.
source: MDN — ETag ↗ -
Explain CORS. Why does a browser block a cross-origin request, and what is a preflight?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism on top of the same-origin policy. By default a page at origin A cannot read responses from origin B unless B opts in via
Access-Control-Allow-Origin.For non-simple requests (custom headers, methods like PUT/DELETE, certain content types) the browser first sends a preflight
OPTIONSrequest. The server answers withAccess-Control-Allow-Methods,Allow-Headers, andAllow-Origin; only then does the browser send the real request.Follow-ups they push on- Does CORS protect the server? (No — it protects the user's browser.)
- What does Access-Control-Allow-Credentials change, and why can't you combine it with '*'?
Red flag Believing CORS is server-side security. It is enforced by the browser; curl, Postman, and a malicious server-to-server call ignore it entirely.
source: MDN — Cross-Origin Resource Sharing (CORS) ↗ -
What does HTTPS/TLS actually add over HTTP, and what is the rough handshake?
TLS adds three things: confidentiality (traffic is encrypted), integrity (tampering is detected), and server identity (the certificate, signed by a CA, proves you are talking to the real host).
Handshake sketch: client sends ClientHello (supported ciphers); server returns its certificate and key-exchange parameters; they use asymmetric crypto (e.g. ECDHE) to agree on a shared symmetric session key; the rest of the connection uses fast symmetric encryption. TLS 1.3 cuts this to roughly one round trip.
Follow-ups they push on- Why switch to a symmetric key after the handshake?
- What does the CA actually vouch for?
Red flag Saying TLS 'encrypts with the certificate'. The cert carries the public key and identity; the bulk data is encrypted with a negotiated symmetric session key.
source: Cloudflare — What happens in a TLS handshake? ↗ -
PUT vs PATCH vs POST for updating a resource — when do you use each?
PUT replaces the resource wholesale and is idempotent — send the full representation. PATCH applies a partial modification (only the changed fields) and is not guaranteed idempotent by spec. POST creates a new subordinate resource or triggers a non-idempotent action.
Rule of thumb: full replace at a known URL → PUT; partial field update → PATCH; create-and-let-the-server-assign-the-id → POST.
Follow-ups they push on- Can PUT create a resource? (Yes, if the client picks the URL/id.)
- How would you make PATCH idempotent?
Red flag Using PUT for partial updates — sending only some fields with PUT semantically blanks the rest. Use PATCH for partial.
source: MDN — PUT ↗ -
A client gets a 200 but you suspect the response was served stale. Which headers control caching, and how would you debug it?
Caching is governed by
Cache-Control(max-age,no-store,no-cache,private/public,must-revalidate), plus validatorsETag/Last-Modifiedand the legacyExpires.Debug path: inspect the response
Cache-ControlandAgeheaders; check whether an intermediary (CDN/proxy) addedAgeor anX-Cache: HIT.no-cachemeans 'revalidate before use', not 'do not cache' — that surprises people. To force freshness setno-storeor a shortmax-ageplus an ETag so clients revalidate cheaply with 304s.Follow-ups they push on- no-cache vs no-store — exact difference?
- What does the Vary header do for a shared cache?
Red flag Reading `no-cache` as 'never cache'. It means store but revalidate every time; `no-store` is the one that forbids storing.
source: MDN — Cache-Control ↗ -
What problems did HTTP/2 solve over HTTP/1.1, and what does HTTP/3 change?
HTTP/1.1 suffers head-of-line blocking: one response per connection at a time, so browsers open many TCP connections. HTTP/2 adds multiplexing (many concurrent streams over one connection), header compression (HPACK), and server push, removing application-layer HOL blocking.
But HTTP/2 still rides TCP, so a single lost packet stalls all streams (transport-layer HOL blocking). HTTP/3 runs over QUIC (UDP), giving independent streams, faster connection setup (0-RTT), and seamless connection migration across network changes.
Follow-ups they push on- Why doesn't HTTP/2 multiplexing fully fix HOL blocking?
- What does QUIC do that TCP can't?
Red flag Claiming HTTP/2 eliminated all head-of-line blocking. It removed it at the HTTP layer but TCP still serializes loss recovery — that's why HTTP/3 moved to QUIC.
source: Cloudflare — HTTP/3 vs HTTP/2 ↗ -
401 vs 403 vs 404 — when do you return each, and why might a security-conscious API return 404 instead of 403?
401 Unauthorized means 'I don't know who you are' — no/invalid credentials; the right fix is to authenticate. 403 Forbidden means 'I know who you are, but you're not allowed' — re-authenticating won't help. 404 Not Found means the resource doesn't exist.
The security twist: a 403 on a resource you can't see still confirms it exists, leaking information (resource enumeration). Some APIs deliberately return 404 instead of 403 for unauthorized access to private resources, so an attacker can't distinguish 'exists but forbidden' from 'doesn't exist'.
What a strong answer covers401 = not authenticated (who are you?); 403 = authenticated but not permitted.
Despite the name, 401 means unauthenticated, not unauthorized — a historical misnomer.
403 confirms a resource exists, which can leak information.
Returning 404 for forbidden private resources prevents enumeration.
Quick self-checkA logged-in user requests another user's private profile they have no rights to see. What's the most information-leak-resistant response?
-
Wrong — the user IS authenticated; 401 means no/invalid credentials.
-
Correct semantically, but it confirms the resource exists, enabling enumeration.
-
Correct for leak resistance — the attacker can't tell 'forbidden' from 'nonexistent'.
-
Wrong — 200 implies success and may confuse clients about whether data exists.
Follow-ups they push on- Why is 401's name a misnomer?
- When would leaking 'this resource exists' actually matter?
Red flag Returning 401 when the user IS authenticated but lacks permission — that's 403. And 403 on private resources silently leaks their existence.
source: MDN — 403 Forbidden ↗ -
What do the SameSite, HttpOnly, and Secure cookie attributes each do?
HttpOnly hides the cookie from JavaScript (
document.cookie), so an XSS payload can't read it — it mitigates token theft. Secure sends the cookie only over HTTPS, so it can't leak over plaintext. SameSite controls whether the cookie rides along on cross-site requests:Strictnever sends it cross-site,Laxsends it only on top-level navigations (the modern browser default), andNonesends it always but then requiresSecure.Together they harden a session cookie: HttpOnly+Secure stop theft and eavesdropping; SameSite is the first line of CSRF defense.
What a strong answer coversHttpOnly → unreadable by JS, blunts XSS-based token theft.
Secure → HTTPS-only transmission.
SameSite → controls cross-site sending;
Laxis the default in modern browsers.SameSite=Nonemust be paired withSecureor the browser rejects it.
Quick self-checkWhich cookie attribute most directly mitigates CSRF?
-
No — it blocks JS access (XSS theft) but the cookie is still auto-sent cross-site.
-
No — it only forces HTTPS; it doesn't restrict cross-site sending.
-
Correct — it restricts whether the cookie is sent on cross-site requests, the core CSRF vector.
-
No — it only controls cookie lifetime, not CSRF.
Follow-ups they push on- Why does SameSite=None require Secure?
- Does HttpOnly do anything against CSRF? (No — the cookie is still auto-sent.)
Red flag Thinking HttpOnly prevents CSRF. It stops JS from reading the cookie, but the browser still attaches it automatically on requests — SameSite/CSRF tokens handle CSRF.
source: MDN — Set-Cookie (SameSite) ↗ -
Trick: is GET guaranteed to have no server-side effects? Is it safe to cache and retry a GET?
By the HTTP spec GET is safe (read-only) and idempotent, so intermediaries (browsers, proxies, CDNs) freely cache and retry it. But 'safe' is a *contract you must honor*, not something the protocol enforces — a poorly designed
GET /delete?id=5will happily delete data.The danger: because GETs are prefetched, cached, and retried, a side-effecting GET can be triggered by a link prefetcher, a crawler, or a retry, causing unintended mutations. Mutations belong on POST/PUT/PATCH/DELETE; keep GET strictly read-only.
What a strong answer coversGET is defined as safe + idempotent, but the server must actually honor that.
Caches, prefetchers, and crawlers will issue GETs without user intent.
A side-effecting GET can fire from a prefetch or retry — a real source of bugs/exploits.
Put all mutations behind non-safe methods.
Quick self-checkWhy is implementing a delete behind GET /delete?id=5 dangerous?
-
False and irrelevant — method choice doesn't determine speed here.
-
Correct — GETs are assumed safe and get fetched automatically.
-
False — GET routinely carries query parameters.
-
False — GET is the most cacheable method.
Follow-ups they push on- How could a crawler or link-prefetcher trigger a side-effecting GET?
- What's the difference between 'safe' and 'idempotent' here?
Red flag Believing the protocol enforces GET's safety. It's a contract — a GET that mutates state is valid HTTP but a design bug that prefetchers and caches will exploit.
source: MDN — Safe (HTTP methods) ↗ -
What's the difference between Connection: keep-alive and HTTP/2 multiplexing? Why isn't keep-alive enough?
Keep-alive (persistent connections, default in HTTP/1.1) reuses one TCP connection for multiple sequential requests, avoiding a new handshake each time. But requests on that connection are still serialized — request 2 waits for response 1 (head-of-line blocking), which is why browsers open ~6 parallel connections per host.
HTTP/2 multiplexing interleaves many concurrent request/response streams over a single connection, so a slow response doesn't block the others at the application layer. Keep-alive reuses the pipe; multiplexing lets many requests share it simultaneously.
What a strong answer coversKeep-alive reuses one connection but processes requests sequentially.
HTTP/1.1 pipelining tried concurrency but still suffered HOL blocking and was largely abandoned.
HTTP/2 multiplexing runs concurrent streams over one connection.
Browsers opened ~6 connections per host precisely to work around HTTP/1.1 serialization.
Follow-ups they push on- Why did HTTP/1.1 pipelining never catch on?
- Does HTTP/2 multiplexing eliminate ALL head-of-line blocking? (No — TCP-level remains.)
Red flag Conflating keep-alive with multiplexing. Keep-alive just avoids re-handshaking; it does not allow concurrent in-flight requests on the same connection.
source: MDN — Connection management in HTTP/1.x ↗ -
How does HSTS work, and what attack does it prevent that a redirect from HTTP to HTTPS does not?
HSTS (HTTP Strict Transport Security) is a response header (
Strict-Transport-Security: max-age=...) that tells the browser to only ever contact this host over HTTPS for the given duration — the browser upgrades anyhttp://request tohttps://*before* sending it.A plain 301 redirect from HTTP→HTTPS still sends that first request in cleartext, which a man-in-the-middle can intercept and strip (SSL stripping). HSTS closes that window because, after the first secure visit (or via the preload list), the browser never makes the insecure request at all.
What a strong answer coversHSTS forces the browser to upgrade requests to HTTPS before any cleartext goes out.
A redirect leaves the initial request exposed to SSL-stripping MITM.
The HSTS preload list protects even the very first visit.
Set a long
max-age;includeSubDomainsextends it to subdomains.
Quick self-checkWhat does HSTS protect against that a 301 HTTP→HTTPS redirect alone does not?
-
No — HSTS doesn't validate cert expiry.
-
Correct — HSTS upgrades to HTTPS before sending, eliminating the cleartext first hop.
-
No — XSS is unrelated to transport security.
-
No — entirely unrelated to HSTS.
Follow-ups they push on- What is SSL stripping?
- Why does the HSTS preload list matter for the first-ever visit?
Red flag Assuming an HTTP→HTTPS redirect is fully secure. The first cleartext request before the redirect is interceptable; HSTS (ideally preloaded) is what removes that gap.
source: MDN — Strict-Transport-Security ↗ -
A client uploads a large file and the server responds 100 Continue before the body. What is the Expect: 100-continue mechanism for?
When a client is about to send a large request body, it can send the headers first with
Expect: 100-continueand pause before sending the body. The server inspects the headers (auth, content-length limits, content-type) and replies 100 Continue to greenlight the body, or an error status (e.g. 401, 413) to reject it up front.The point is to avoid wasting bandwidth uploading a huge body that the server would only reject anyway. It's part of the 1xx informational family — a provisional response before the final one.
What a strong answer coversExpect: 100-continuelets the client send headers, then wait for a go-ahead.Server replies 100 Continue to accept the body, or an error to reject before upload.
Saves bandwidth on large bodies the server would reject (auth fail, too large).
1xx are provisional/informational responses preceding the final status.
Follow-ups they push on- What status would the server send instead of 100 to reject an oversized upload? (413)
- What other 1xx codes exist? (101 Switching Protocols, 103 Early Hints)
Red flag Treating 100 Continue as a final response. It's provisional — the real status comes after the body is sent and processed.
source: MDN — 100 Continue ↗