Representative interview topic

Backend Interview: How do ETag and If-Match prevent lost updates?

BackendMedium
Offer.cc Editorial TeamPublished Updated

Question

Two editors load the same document and both submit updates. How would you use ETag and If-Match so the second write cannot silently overwrite the first?

Prompt and setting

An API serves a document with a version validator. Multiple clients can read and edit it concurrently, and the requirement is to reject stale writes while keeping the API retryable and observable.

What the interviewer tests

  • Distinguishing representation caching from write preconditions.
  • Choosing a strong ETag and enforcing If-Match on mutation methods.
  • Returning 412 on a stale version and defining a safe client recovery path.

Clarifying questions before answering

  • Does the ETag represent the exact stored representation or only a weak semantic version?
  • Which methods require a precondition: PUT, PATCH, DELETE, or all writes?
  • Should a client merge fields automatically, or must a human resolve conflicts?
  • Are retries sent through a load-balanced API and a single transactional datastore?

30-second answer framework

I would return a strong ETag with every editable representation. The client sends that value in If-Match on PUT, PATCH, or DELETE. The server compares it inside the same transaction as the update; a mismatch returns 412 without applying the mutation. The response should include the current representation or a refetch signal, while metrics track conflicts and missing preconditions. The client then refetches, merges deliberately, and retries with the new ETag.

Step-by-step deep dive

1. Issue a validator

On GET, return the document and an ETag derived from the canonical stored version. A database revision number can be simpler than hashing a large payload, provided the value changes whenever the representation relevant to the edit changes. Use a strong validator for If-Match; weak validators are unsuitable for protecting exact write state.

2. Enforce the precondition atomically

The update must check the expected version and write the new version as one conditional operation. Conceptually:

sql
UPDATE documents
SET body = :new_body, version = version + 1
WHERE id = :id AND version = :expected_version;

If the affected-row count is zero, return 412 and perform no side effect. Checking the ETag in application memory and writing later creates a race between the check and commit.

3. Separate 412 from other failures

412 means the supplied precondition is false; it is a concurrency conflict, not malformed JSON and not authentication failure. Return 400 for an invalid request shape, 401 or 403 for authorization, and 404 when the resource is unavailable under the API policy. This distinction lets clients choose refetch-and-merge versus user correction.

4. Design the client recovery

After 412, fetch the current document and show the conflicting fields or a diff. An automatic merge is safe only when field-level semantics and authorization rules make it safe. The retry must use the newly returned ETag and remain idempotent at the resource level; never blindly replay the stale request.

5. Keep caches and replicas coherent

Generate validators from committed state visible to the write path. A read from a lagging replica can return an old ETag and cause avoidable conflicts, while a write routed to another node must still enforce the version in the primary transaction. Emit conflict rate, missing-If-Match rate, and retry success metrics.

High-quality sample answer

“I would return a strong ETag for each editable document and require If-Match on mutations. The server would compare the tag in the same transaction that updates the row, using a conditional version update. If no row matches, it returns 412 and applies no side effect. The client refetches, presents a diff or performs a narrowly defined merge, then retries with the new ETag. I would distinguish 412 from validation and authorization errors, and monitor stale-read, conflict, and retry-success rates.”

Common mistakes

  • Check ETag, then update in separate operations → a race remains → perform the version predicate and write atomically.
  • Use a weak validator for exact writes → semantically similar content may compare incorrectly → use a strong validator.
  • Return 409 for every stale write → clients cannot distinguish protocol preconditions → use 412 for a failed If-Match condition.
  • Blindly retry the stale payload → the first editor's changes can be lost → refetch, merge intentionally, and retry with the new tag.

Follow-up questions and responses

Is ETag only for caching?

No. If-None-Match commonly enables cache validation, while If-Match makes a write conditional on the current representation. The same validator can serve both roles if its comparison strength and generation policy are correct.

What if the client omits If-Match?

For resources requiring optimistic concurrency, reject the mutation with a documented precondition-required response or a clear 400 policy. Silently accepting an unconditioned write reintroduces lost updates; the policy must be consistent across methods.

Should the server return the latest document in a 412 response?

It can, if authorization and payload size permit, but the contract should still require a refetch or explicit conflict resolution. Returning data does not authorize the client to overwrite it without using the latest validator.

Public sources

Related questions