What Are Conditional Requests?
Conditional requests let HTTP clients attach preconditions to a request. The server only performs the operation if the precondition evaluates to true — otherwise it returns an appropriate status code without doing work. This enables two critical use cases: cache validation (avoiding redundant data transfer) and optimistic concurrency control (safe concurrent edits).
Validators
Conditional requests rely on two types of validators:
ETags (Entity Tags) — an opaque string representing a resource version:
ETag: "33a64df551425fcc55e"
ETag: W/"33a64df" # Weak ETag (semantic equivalence, not byte-identical)
Last-Modified — a timestamp of the last change:
Last-Modified: Wed, 26 Feb 2026 10:30:00 GMT
Strong ETags guarantee byte-identical responses. Weak ETags (prefixed with W/) indicate semantic equivalence — the response conveys the same information but might differ in headers like Date. Use strong ETags for byte-range requests; weak ETags are fine for cache validation.
Cache Validation with If-None-Match
The most common conditional request pattern avoids re-downloading unchanged resources:
# 1. First request: server includes ETag in response
GET /api/users/42 HTTP/1.1
HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: application/json
{...full user object...}
# 2. Subsequent request: client sends ETag back
GET /api/users/42 HTTP/1.1
If-None-Match: "abc123"
# 3a. Resource unchanged → 304 (no body, saves bandwidth)
HTTP/1.1 304 Not Modified
ETag: "abc123"
# 3b. Resource changed → 200 with new body
HTTP/1.1 200 OK
ETag: "def456"
{...updated user object...}
The If-Modified-Since header serves the same purpose using timestamps:
If-Modified-Since: Wed, 26 Feb 2026 10:30:00 GMT
ETags are preferred because timestamps have 1-second resolution and can fail when multiple servers generate the same content at different times.
Optimistic Concurrency with If-Match
For write operations, If-Match prevents the lost update problem — two clients editing the same resource simultaneously, where the second save silently overwrites the first:
# Client A and Client B both fetch the resource:
GET /api/documents/99
→ ETag: "v1"
# Client A updates successfully:
PUT /api/documents/99
If-Match: "v1"
→ 200 OK, new ETag: "v2"
# Client B tries to update with stale ETag:
PUT /api/documents/99
If-Match: "v1" # Server now has "v2"
→ 412 Precondition Failed
On 412 Precondition Failed, the client must re-fetch the resource, show a conflict UI, and let the user merge changes before retrying.
# Django view implementing If-Match:
def update_document(request, pk):
doc = get_object_or_404(Document, pk=pk)
current_etag = f'"{doc.version_hash}"'
if_match = request.headers.get('If-Match')
if if_match and if_match != current_etag:
return HttpResponse(status=412)
# Safe to update
doc.update(request.data)
return JsonResponse(doc.to_dict(), headers={'ETag': f'"{doc.version_hash}"'})
If-Unmodified-Since
The timestamp equivalent of If-Match. Less reliable due to clock skew and 1-second granularity, but useful as a fallback when the server does not generate ETags.
Conditional Range Requests with If-Range
If-Range combines conditional logic with partial content for resumable downloads:
# Resume a download after interruption:
GET /files/large-dataset.csv
Range: bytes=1048576-
If-Range: "v1-etag"
# If resource unchanged: 206 Partial Content (resume works)
HTTP/1.1 206 Partial Content
# If resource changed: 200 OK with full content (restart download)
HTTP/1.1 200 OK
Without If-Range, a changed resource would produce corrupt output (old start bytes + new end bytes). If-Range ensures the client gets the full new file when the resource has changed.
Implementation Patterns
Django
Django's ConditionalGetMiddleware automatically handles If-None-Match and If-Modified-Since for GET requests when views set ETag or Last-Modified headers:
# settings.py — enable middleware
MIDDLEWARE = [
'django.middleware.http.ConditionalGetMiddleware',
# ...
]
# views.py — set ETag in view
from django.views.decorators.http import etag
def compute_etag(request, pk):
obj = get_object_or_404(Article, pk=pk)
return obj.updated_at.isoformat()
@etag(compute_etag)
def article_detail(request, pk):
obj = get_object_or_404(Article, pk=pk)
return JsonResponse(obj.to_dict())
Express / Node.js
const etag = require('etag');
app.get('/api/users/:id', (req, res) => {
const user = db.findUser(req.params.id);
const tag = etag(JSON.stringify(user));
if (req.headers['if-none-match'] === tag) {
return res.status(304).end();
}
res.set('ETag', tag).json(user);
});
CDN Behavior
CDNs automatically participate in conditional request flows. When a CDN has a cached response with an ETag, it forwards client If-None-Match requests to the origin only when its own cached copy has expired. This dramatically reduces origin load while keeping client caches accurate.