Skip to main content

HTTP Semantics: Methods, Status Codes & Caching Headers

Level 1: Level 1: Foundations & Mental Modelsmedium30 minhttpapi-designcachingconcurrency

Use safe/idempotent method semantics to drive retries and caching, conditional GETs with ETag for cheap 304s, and ETag + If-Match for optimistic concurrency.

Decades of distributed-systems thinking, already encoded

HTTP already encodes decades of distributed-systems thinking about safety, idempotency, caching, and concurrency. Using its semantics correctly gets you free caching and safe retries; ignoring them silently loses data.

Methods: safe and idempotent are orthogonal

Safe means read-only (no server state change): GET and HEAD. Idempotent means repeating it lands the same final state: GET, HEAD, PUT, DELETE. POST is neither safe nor idempotent, PATCH generally is not idempotent. This directly drives retry behavior: an intermediary or client can safely auto-retry GET/PUT/DELETE after a network blip, but must not blindly auto-retry POST (that is what idempotency keys are for). Safe methods are also the cacheable ones.

Check yourself

A network blip cuts the connection before the response arrives, so the client cannot tell whether the server did the work. Sort each call by whether the client can safely fire it again.

'GET' a document
'PUT' the full updated document
'DELETE' a document
'POST' to create an order
'PATCH' that appends an entry to a list

Status families are a contract with the client:

  • 2xx success: 200 OK, 201 Created (return a Location header pointing at the new resource), 204 No Content.
  • 3xx: redirects and, importantly, 304 Not Modified for conditional requests.
  • 4xx client error: 400, 401, 403, 404, 409 conflict, 422 unprocessable, 429 rate limited. Do not retry these blindly.
  • 5xx server error: 500, 503. Retry with backoff.

Read caching: Cache-Control plus a validator

On a GET you send Cache-Control (max-age for private/browser caches, s-maxage for shared/CDN caches, no-store for sensitive data) plus a validator: an ETag (an opaque version hash) or Last-Modified timestamp. The validator enables the conditional GET: the client sends If-None-Match: <etag> (or If-Modified-Since), and if nothing changed the server returns 304 Not Modified with no body. That saves bandwidth and origin rendering while keeping the client current.

Optimistic concurrency: ETag + If-Match

The same ETag gives you optimistic concurrency control, which prevents the lost-update problem. Two editors both GET a document (ETag v5). Editor A saves with If-Match: v5; the server sees the current version is still v5, applies the write, and the ETag becomes v6. Editor B then saves with If-Match: v5; the server sees the current version is now v6, refuses, and returns 412 Precondition Failed. B is forced to re-read and merge instead of silently clobbering A's change. This is far cheaper than pessimistic locking and is exactly how you avoid last-write-wins data loss.

Content negotiation completes the picture: honor Accept and Accept-Language, and set Vary: Accept, Accept-Encoding on responses so a shared cache does not serve a JSON body to a client that asked for XML, or a Brotli body to a client that cannot decode it.

Interview nuance: the two high-signal moves are (1) tying method idempotency to retry safety, and (2) describing ETag + If-Match -> 412 as optimistic concurrency to prevent lost updates. Saying "return 200 and last-write-wins" is the wrong turn interviewers listen for.

Recap: use safe/idempotent method semantics to drive retry and caching, add Cache-Control plus ETag for cheap conditional GETs (304), and use ETag + If-Match -> 412 for optimistic concurrency that prevents lost updates.

Check yourself
Two editors both fetch a document at version 'v5'. Editor A saves a change; moments later Editor B saves an edit still based on 'v5'. Your API returns '200 OK' to both and applies each write as it arrives. What happened to A's change?

Apply

Your turn

The task this lesson builds to.

Design the HTTP semantics for a document API: choose methods and status codes for read, create, update, and delete, and explain how you would use ETag, If-None-Match, and If-Match to cache reads and prevent lost updates.

Think about

  1. Which methods are safe, which are idempotent, and why does that distinction drive retry behavior?
  2. How do Cache-Control, ETag, and Last-Modified turn a GET into a cheap conditional request?
  3. How does ETag plus If-Match give you optimistic concurrency, and what status code signals a conflict?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design the HTTP concurrency and caching model for Google Docs-style collaborative editing where dozens of users edit the same document simultaneously, edits must not be lost, and reads should be cheap. Explain where simple ETag + If-Match optimistic concurrency is sufficient and where it breaks down, and what you would use instead.

Think about

  1. What contention level does optimistic concurrency assume, and does the document body meet it?
  2. Which writes on this product are actually low-contention and ETag-friendly?
  3. What protocol family merges concurrent edits instead of rejecting them?