Conditional Requests
title: "Conditional Requests — ETag and If-Modified-Since for API Caching" description: "Conditional requests use ETag and If-Modified-Since headers to let clients check if cached content changed, returning 304 Not Modified to save bandwidth." date: 2026-06-28 lastmod: 2026-06-28 weight: 15 tags: [apis, caching] }
Conditional requests allow clients to validate cached responses using ETag (content hash) or If-Modified-Since (timestamp), returning 304 with no body if unchanged.
What You'll Learn
- ETag generation and comparison
- If-Modified-Since usage
- Strong vs weak ETags
Why It Matters
Conditional requests reduce bandwidth to near zero for unchanged resources. Even if cache TTL expires, revalidation uses minimal overhead.
Conditional Request Flow
sequenceDiagram
Client->>Cache: GET /resource
Cache->>Origin: GET /resource (If-None-Match: "abc")
Origin->>Origin: Check resource hash
Origin-->>Cache: 304 Not Modified
Cache-->>Client: 200 (from cache)
Code Examples
import hashlib
# Strong ETag from content hash
def compute_etag(data):
return hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest()
@app.route('/api/articles')
def get_articles():
articles = db.get_articles()
etag = compute_etag(articles)
if request.headers.get('If-None-Match') == etag:
return '', 304
response = jsonify(articles)
response.headers['ETag'] = etag
response.headers['Cache-Control'] = 'public, max-age=300'
return response
# Weak ETag for semantically equivalent content
def compute_weak_etag(data):
# W/ prefix indicates weak ETag
return f'W/"{hashlib.md5(str(data).encode()).hexdigest()}"'
// Conditional request with If-Modified-Since
app.get('/api/articles', (req, res) => {
const articles = db.getArticles();
const lastModified = getLastModified(articles);
const etag = crypto.createHash('sha256').update(JSON.stringify(articles)).digest('hex');
// Check If-Modified-Since
const ifModifiedSince = req.headers['if-modified-since'];
if (ifModifiedSince) {
const clientTime = new Date(ifModifiedSince).getTime();
if (lastModified.getTime() <= clientTime) {
return res.status(304).end();
}
}
// Check If-None-Match
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.set({
'ETag': etag,
'Last-Modified': lastModified.toUTCString(),
'Cache-Control': 'public, max-age=300'
});
res.json(articles);
});
Common Mistakes
1. Weak ETag for Exact Comparisons
Weak ETags (W/"...") allow byte-level differences. Use strong ETags for exact content.
2. No ETag When Content Changes Frequently
ETags work even for frequently changing content — they just expire faster.
3. Using Last-Modified Without ETag
Use both. Last-Modified has second granularity; ETags catch sub-second changes.
4. Case-Sensitive ETag Comparison
Always use case-insensitive ETag comparison (except for quoted strings).
5. ETag Without Cache-Control
ETag validation only happens after cache expiry. Set appropriate TTL.
Practice Questions
- What is a strong vs weak ETag?
- How does If-None-Match work with ETag?
- What is the benefit of conditional requests?
- What happens when ETag matches?
- Should you use ETag, Last-Modified, or both?
Answers:
- Strong ETags compare byte-for-byte; weak ETags compare semantically.
- If ETag matches, server returns 304 with no body.
- Bandwidth reduction — only headers, no body, are transferred on validation.
- Server returns 304 Not Modified with empty body.
- Both. ETag for precision, Last-Modified for legacy clients.
Challenge: Implement ETag generation for a database-backed API. Use row version numbers or content hashes. Test with conditional requests and measure bandwidth savings.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro