Prompt and scope
This question tests whether a backend engineer can place concurrency control at the HTTP boundary. Explain the difference between cache validation and write protection, how the server compares entity tags, which statuses it returns, and how database writes and response headers stay aligned.
What the interviewer is evaluating
- Whether you understand the different meanings of ETag, If-Match, and If-None-Match.
- Whether a strong validator protects writes from stale versions.
- Whether 412, 428, and 409 have distinct precondition and business-conflict boundaries.
- Whether you account for proxy caches, retries, authorization, and replica visibility.
Clarifying questions to ask first
Confirm whether updates replace the whole resource or only fields, whether clients must send a version, whether offline editing and merge are supported, whether the ETag represents the full representation or a business version, and whether reads and writes pass through caches, load balancers, or multiple database replicas.
A 30-second answer structure
Return an ETag with every resource representation. Require If-Match on mutations, compare it with the current version inside the write boundary, and return 412 on mismatch or 428 when a required precondition is missing. On success, increment the version and return a new ETag. GET with If-None-Match may return 304, but cache validation does not protect writes.
Deep-dive answer
1. Separate cache validation from write protection
If-None-Match is useful for GET cache validation: an unchanged representation can produce 304. If-Match requires the current representation to match and is common on PUT, PATCH, or DELETE. A cache hit does not grant permission to overwrite the current resource; the comparison direction and failure behavior differ.
2. Choose an ETag that is safe to compare
Use a strong ETag for write protection so the bytes or defined version match exactly. Weak ETags suit semantically equivalent cache representations and should not authorize precise updates. The tag should not expose an internal sequence or sensitive data; derive it from a canonical representation and version with an unpredictable salt, after applying authorization filtering.
3. Keep comparison and update in one boundary
Read the current version, compare If-Match, and update in one conditional database write such as UPDATE ... WHERE id = ? AND version = ?. Zero affected rows means 412; a successful update increments the version and produces the new ETag. Do not query in application code and then issue an unconditional write.
4. Design failure responses and retry paths
Return 428 when a required precondition is absent, 412 when the ETag does not match, and 409 for a separate business-state conflict. Include the current representation or a re-fetch hint, but do not silently overwrite for the client. After 412, the client should GET again, show a diff, or apply an explicit field merge and retry with the new ETag; automatic retries need bounded attempts and clear idempotency.
5. Handle caches, replicas, and operations
The read that creates an ETag must see the latest write, or the system must define a bounded read-after-write delay. Proxies must forward ETag, If-Match, and If-None-Match correctly, with suitable cache controls for sensitive resources. Log request ID, old and new versions, outcome, and conflict rate to find clients using stale representations or lagging replicas.
Example of a strong answer
I would return a strong ETag with every GET, derived from a canonical representation and version. PUT, PATCH, and DELETE require If-Match; the database performs a version-conditional update, returning 412 when no row matches. Missing preconditions follow the interface policy and return 428, while success increments the version and returns the new ETag. GET with a matching If-None-Match returns 304 for cache savings only. After 412, the client re-reads, shows a diff or performs an explicit field merge, and retries with the new tag. Reads across replicas must see the current version, and proxies must forward conditional headers. Monitor conflicts, stale-version requests, and replica lag.
Common mistakes
- Comparing only client timestamps and ignoring clock skew or representation differences.
- Using a weak ETag to authorize an exact write.
- Querying the version first and then writing without a condition, leaving a race.
- Treating 412, 409, and 428 as one generic error that gives clients no next step.
- Letting the server silently overwrite a conflict and lose the client’s edit.
- Generating tags only at application nodes without considering cache and replica visibility.
Follow-up questions
Can an ETag be a database auto-increment version?
It can be an internal input, but encode the value for HTTP and avoid exposing sensitive business information. If the representation changes with field filtering or authorization, generate the tag at that representation boundary.
Can two non-overlapping PATCH fields be merged automatically?
Offer an explicit field-level merge protocol, but still validate which ETag the client used. Publish merge and non-merge rules and audit the decision; different fields do not make every business conflict safe to ignore.
Why not return only 409?
412 means an If-Match-style precondition failed, 428 asks the client to provide one, and 409 describes a separate conflict with current resource state or a business operation. The distinction tells the client whether to re-fetch, add a header, or change the action.
How do you avoid stale writes through a CDN?
Route mutations to the authoritative origin and use caches for reads. Forward conditional headers according to the specification, invalidate affected entries after a successful update or bound staleness, and monitor origin-version versus edge-tag differences.