Prompt and context
This question tests backend APIs, object storage, and concurrency control. AWS conditional requests add preconditions to PutObject, CompleteMultipartUpload, and CopyObject: creation can require that a key does not exist, while an update can require that the ETag is unchanged. The core issue is making storage evaluate the check with the write, rather than letting clients perform a racy HEAD followed by PUT.
What the interviewer is testing
A strong answer separates creation from update, chooses If-None-Match: * or If-Match with the current etag-value, and maps a failed condition to retry or merge behavior. It also covers multipart completion, weak ETags, uncertain network outcomes, and audit signals. “Add a lock” does not explain multiple processes or failure recovery.
Clarifying questions to ask first
Write semantics
Is this a write-once immutable object, or an update based on a version that was read? The first uses an existence precondition; the second needs a version ETag.
Conflict policy
Should a conflict return 412 so the client rereads and merges, or should the attempt be abandoned? A manifest or registry usually must not be silently overwritten.
Upload path
Will small objects use PutObject and large ones multipart? Put the condition on the final completion request and define interruption, retry, and cleanup behavior.
30-second answer framework
“I first determine whether this is creation or versioned update. Creation uses If-None-Match: *, so S3 atomically rejects an existing key. An update reads the current ETag and sends If-Match; if the ETag changed, S3 returns 412 and the client rereads or merges. Multipart uploads apply the condition at CompleteMultipartUpload. A timeout is not proof of failure, so I query the object or use an idempotency record before retrying, and monitor 412s, orphaned parts, and retry counts.”
Step-by-step deep answer
Step 1: Remove check-then-act
A HEAD followed by PUT races: two clients can both observe absence and then write. Put the precondition in the write request so the storage service evaluates it against the current object state.
Step 2: Choose the condition
Use If-None-Match: * for an immutable create; it succeeds only when no current representation exists. Use strong If-Match for an existing object, allowing the write only when the ETag equals the version read. A mismatch returns 412 and protects newer content.
Step 3: Define the conflict protocol
Do not blindly retry a 412. GET the current object and determine whether the client change can merge; otherwise create a conflict record or a new key. Add backoff and a retry cap so a hot key does not create a retry storm.
Step 4: Cover multipart and copy
Another client can create the same key while parts are uploading, so the final CompleteMultipartUpload needs the condition too. Copy or replace workflows should carry the ETag condition, while lifecycle rules clean up abandoned multipart uploads.
Step 5: Handle timeouts and observe
A timeout does not reveal whether the write committed. Query the object and ETag before retrying, and record a client command ID for create intent. Monitor 412 rate, successful retry rate, orphaned parts, and version drift to verify conflicts are rejected rather than overwritten.
High-quality sample answer
I would separate immutable data files from an updateable registry. A data-file create uses If-None-Match: *, so only one same-key writer succeeds. The registry is read with an ETag and updated with If-Match; a 412 triggers a reread and merge instead of overwriting another version. A large upload applies the condition at CompleteMultipartUpload, and a cleanup job reclaims failed uploads. After a network timeout, I query the object and ETag before retrying. S3 supplies the atomic conflict check; the client owns merge policy and idempotency records. I would validate it with 412, retry, and orphan-part metrics.
Common mistakes
- Mistake: HEAD to check absence, then PUT. → Why it fails: Two clients can pass the check concurrently. → Fix: Use
If-None-Match: *on PUT. - Mistake: Use
If-None-Matchfor an update. → Why it fails: It means “must not exist,” not “must equal the version I read.” → Fix: Read a strong ETag and useIf-Match. - Mistake: Retry the original request unconditionally after 412. → Why it fails: It can overwrite repeatedly or create a retry storm. → Fix: Reread, merge, or abandon with bounded backoff.
- Mistake: Apply the condition only to the first multipart request. → Why it fails: The object state can change before completion. → Fix: Apply it again at
CompleteMultipartUpload.
Follow-ups and responses
Follow-up 1: Can ETag be treated as a content hash?
Not universally. The requirement is a version validator; multipart and encryption scenarios can change ETag semantics, so use the service-documented checksum or validator for that operation.
Follow-up 2: How do 412 and 409 differ?
Follow the API contract: 412 means a request precondition failed and usually asks the client to reread; another conflict status may describe resource or operation state. Do not infer semantics from the number alone.
Follow-up 3: What if the client cannot merge?
Preserve the current version and create a conflict record or new key for a domain owner to resolve. Silent overwrite is not a success criterion.
Follow-up 4: How do you test concurrency correctness?
Run concurrent creates and updates on one key, inject timeout, duplicate multipart completion, and cleanup failures, then verify at most one create succeeds, old content never overwrites newer content, and audit records match the outcomes.