A conditional request tells the server 'perform this request only if some condition holds.' You send a precondition in the request headers; the server evaluates it and either proceeds normally or replies with a special status code. This shines in two places — cache revalidation (don't re-download what hasn't changed) and optimistic concurrency control (don't overwrite someone else's edit).
Validators: ETag and Last-Modified
To judge a condition, you need a validator that identifies the resource's 'version.' An ETag (entity tag) is an opaque version fingerprint derived from the content, given as a quoted string (e.g. ETag: "a1b2c3"). Last-Modified is the time the resource last changed. The server sends these validators on a response, and the client stores them to echo back on the next conditional request.
If-None-Match: Get the body only if it differs from your storedETag— cache revalidation (GET).If-Modified-Since: Get the body only if changed since the given time —Last-Modified-based revalidation.If-Match: Apply the write only if it matches theETagyou know — optimistic locking (writes).If-Unmodified-Since: Proceed only if unchanged since the given time — time-based locking.If-Range: Return the partial (206) only if the validator is unchanged, else the whole thing (200) — the resumable-download safeguard.
Strong vs weak ETags
ETags come in two flavors. A strong ETag matches only when the bytes are exactly identical — used where precise byte equality matters, such as range requests. A weak ETag is prefixed with W/ (e.g. W/"a1b2c3") and treats representations as equal if they're 'semantically equivalent.' For example, two responses differing only in whitespace or a timestamp but effectively the same content may share a weak ETag. Weak validation is also handy when representations differ (compressed vs identity) yet are the same resource.
Cache revalidation: If-None-Match and 304
The most common conditional request is cache revalidation. The client sends its stored ETag in If-None-Match, asking 'give me the body only if it differs from this version.' If the content is unchanged, the server returns just 304 Not Modified with no body, and the client reuses its cache. If it changed, the server sends 200 with the full body and a new ETag. With Last-Modified, the counterpart header is If-Modified-Since.
GET /api/doc/42 HTTP/1.1
Host: example.com
If-None-Match: "a1b2c3"
HTTP/1.1 304 Not Modified
ETag: "a1b2c3"
Cache-Control: max-age=0
Optimistic concurrency: If-Match and 412
The second use is preventing write conflicts. If-Match is the inverse of If-None-Match: 'update only if it's still the version I know.' The client echoes the ETag it received when reading the document into the If-Match of its update; the server applies the change only if the current version matches, and rejects with 412 Precondition Failed if someone else edited it first and the versions diverged.
PUT /api/doc/42 HTTP/1.1
Host: example.com
If-Match: "a1b2c3"
Content-Type: application/json
{"title":"updated"}
HTTP/1.1 412 Precondition Failed
The lost-update problem
Why do we need this? Say two users, A and B, open the same document at once. A saves first, then a moment later B — unaware of A's change — saves based on their stale copy, and A's edit vanishes without a trace. That is the lost-update problem. Optimistic locking via If-Match/ETag prevents it elegantly: it optimistically assumes no conflict in the common case, and rejects with 412 only at the moment versions actually diverge, prompting the client to re-read and merge. Unlike pessimistic locking that holds a database row, optimistic locking grabs no resource while reading, so it scales and stays responsive.
In short, conditional requests achieve two goals with one elegant mechanism: eliminating needless transfers for performance (304) and preventing invisible data loss for correctness (412). When designing a REST API, strongly consider providing an ETag on every resource and requiring If-Match on update endpoints.