General interview: How does HTTP 428 Precondition Required prevent lost updates?
Prompt and context
Two clients edit the same document. The server requires conditional updates: it returns 428 when the condition is missing and 412 when the supplied condition no longer matches. Explain how ETag, If-Match, caching, and retries work together so a later write cannot silently overwrite an earlier one.
What the interviewer evaluates
- Distinguishing 428 as a policy requirement from 412 as a failed evaluated condition.
- Using a strong ETag and If-Match for optimistic concurrency control.
- Designing read, edit, submit, conflict-display, and merge steps.
- Handling caching, idempotency, auditability, and retry boundaries.
Questions to clarify before answering
- Which resources and write methods require conditions, and is
If-Match: *allowed? - Is the ETag strong or weak, and does it change with every protected representation update?
- How should the client handle 428, 412, 404, and 409 respectively?
- Are conflicts merged by field, chosen by a user, or abandoned?
- What are the cache, version, idempotency-key, and audit requirements?
30-second answer framework
The client GETs the resource and saves its ETag, then submits the edit with If-Match. A missing condition gets 428, telling the client to add one; a mismatch gets 412, telling it the resource changed. The client rereads, shows the difference, merges, and submits with the new ETag instead of overwriting blindly. The server compares a strong ETag atomically, records versions and audit data, and keeps conditional caching separate from write protection.
Step-by-step deep dive
Step 1: Separate 428 and 412
428 is a server policy response: the request omitted a required precondition. 412 means a supplied condition was evaluated and the current resource state did not satisfy it. Neither means to retry the original request unchanged.
Step 2: Generate a resource version
The server emits a strong ETag and changes it whenever the protected representation changes. The client stores that ETag from GET as its edit baseline instead of relying only on a local timestamp.
Step 3: Submit If-Match
The client sends If-Match with PUT, PATCH, or another side-effecting update. Before applying the write, the server atomically compares the current ETag; only a match proceeds, while a mismatch returns 412 and leaves the resource unchanged.
GET /documents/42
ETag: "v17"
PUT /documents/42
If-Match: "v17"Step 4: Resolve the conflict
After 412, GET again and show the server version beside local changes. Merge fields when the policy permits; otherwise ask the user to choose. Never resend the stale If-Match value.
Step 5: Design cache behavior
Conditional GET can use If-None-Match and receive 304, while write protection uses If-Match. Caches must not treat an old ETag as current or cache an error in a way that reveals resource state.
Step 6: Handle failures and retries
After a network timeout, do not blindly replay a non-idempotent write. Query the resource version or use an idempotency key to establish the outcome. A 428 needs a precondition; a 412 needs a reread and merge.
Step 7: Record and verify
Track versions, ETags, conflict counts, automatic-merge rate, and user abandonment. A concurrent-update test should show one writer succeeding and the other receiving 412, with an audit trail for every version change.
High-quality sample answer
I would return a strong ETag such as "v17" from GET and have the client retain it during editing. A PUT without If-Match returns 428 with an instruction to use a conditional update; a stale ETag fails the atomic comparison and returns 412. The client then GETs the latest version, displays the differences, merges, and retries with the new ETag rather than replaying the stale condition. If-None-Match and 304 optimize reads but do not protect writes. A timed-out write is checked by version or idempotency key before any replay, and we monitor conflict and merge rates.
Common mistakes
- Treating both 428 and 412 as transient errors and retrying forever.
- Replacing strong ETag comparison with a client timestamp.
- Overwriting the server version after receiving 412.
- Confusing If-None-Match cache validation with If-Match write protection.
- Replaying a side-effecting write unconditionally after a timeout.
Follow-ups and responses
Follow-up 1: Why not use Last-Modified?
Timestamp precision and clock issues can make two versions look equal. A strong ETag binds directly to the representation version and is better for preventing lost updates.
Follow-up 2: When is If-Match: * useful?
It expresses that the resource must exist or prevents creating or replacing against an unknown version, depending on the API contract. It is not an unconditional write.
Follow-up 3: How is 412 different from 409?
412 means an HTTP precondition was not satisfied. 409 means the request conflicts with the current resource state at the business level. An API may use both but should state the recovery action.
Follow-up 4: How would you merge at field level?
Load the common baseline, server version, and local version, then merge according to field policy. Send same-field conflicts to the user and submit the result with a new ETag.
Follow-up 5: What if a cache returns an old ETag?
Use suitable cache control or revalidation before editing and force an origin read after a failed update. The server's atomic comparison with the current version remains authoritative.
Follow-up 6: How do you test concurrency safety?
Have two clients read one ETag and write concurrently. Verify exactly one succeeds and the other receives 412, then cover retries, timeouts, caches, and the audit path.