DynamoDB Optimistic Concurrency with Conditional Writes
Use ConditionExpression on DynamoDB writes to enforce atomic compare-and-swap, plus TransactWriteItems for multi-item invariants.
01 Apr 2026, 03:41 UTC

DynamoDB Optimistic Concurrency with Conditional Writes
When two writers update the same DynamoDB item, a plain PutItem or UpdateItem silently overwrites the latest state. The useful takeaway is to attach a ConditionExpression to the write so DynamoDB compares the current item against an expected value before mutating anything. If the comparison fails, the service returns ConditionalCheckFailedException and leaves the item unchanged. This gives atomic compare-and-swap behavior without a separate lock table.
How the condition is evaluated
DynamoDB evaluates ConditionExpression server-side against the item as it exists at write time. Use ExpressionAttributeNames to substitute attribute names, especially reserved words such as version, status, or key. Use ExpressionAttributeValues for the comparison values. The expression can reference the primary key and any top-level attribute, but it cannot reference a secondary index.
Worked example: conditional version increment
Run this UpdateItem call from application code or the AWS CLI. Required permissions include dynamodb:UpdateItem on the target table. Replace MyTable and item-123 with your table and partition key value. Risk: a mismatched condition still consumes read capacity while rejecting the write, so tight retry loops can throttle the table.
aws dynamodb update-item \\
--table-name MyTable \\
--key '{"pk": {"S": "item-123"}}' \\
--update-expression "SET #v = :new" \\
--condition-expression "attribute_exists(pk) AND #v = :old" \\
--expression-attribute-names '{"#v": "version"}' \\
--expression-attribute-values '{":old": {"N": "7"}, ":new": {"N": "8"}}' \\
--return-values ALL_NEW
If the committed version is 7, DynamoDB sets it to 8 and returns the updated item. If another writer already changed version to 9, the condition fails, DynamoDB raises ConditionalCheckFailedException, and no write occurs. The read, comparison, and update happen as one server-side operation, so there is no gap for a lost update.
Idempotent retries
Because the condition pins the write to an observed version, a client can safely retry after reading the latest item. A successful write advances the version, so a second attempt with the same stale :old value fails predictably. Treat ConditionalCheckFailedException as a normal conflict outcome: read the current item, recompute the intended transition, and retry with bounded backoff, or surface the conflict to the caller.
Multi-item invariants with TransactWriteItems
A condition applies to one item only. To enforce a rule across multiple items, such as transferring a balance between two accounts, use TransactWriteItems. Each action in the transaction can carry its own ConditionExpression, and the service applies all actions or none. Limits include a maximum of 25 actions per request and a 4 MiB total request payload. Required permission is dynamodb:TransactWriteItems.
aws dynamodb transact-write-items \\
--transact-items file://transaction.json
Keep the transaction small. A conflicting write on any item in the transaction causes the entire request to abort, so design the client to retry the whole transaction from freshly read state.
Limits and common mistakes
- Expression length: the combined UpdateExpression and ConditionExpression must stay under 4 KB.
- Item size: the resulting item must remain below 400 KB.
- Transaction size: TransactWriteItems allows at most 25 actions and 4 MiB per request.
- Reserved words: version, status, key, and similar words must be aliased through ExpressionAttributeNames.
- Read consistency: conditions cannot be attached to reads. If your decision depends on the latest committed value, use a strongly consistent read or a transaction instead of an eventually consistent read.
- Skipping ConditionExpression and relying on application-level checks, which allows concurrent overwrites.
- Using a raw attribute name like version in the expression without an alias, causing a validation error.
- Assuming a failed conditional write consumes no capacity. The request still reads the current item to evaluate the condition, so repeated failures can consume provisioned throughput.
- Replacing a client idempotency token with a condition. The condition prevents lost updates but does not by itself stop a client from submitting the same business action twice under different versions.
Verification steps
- Create a test table with partition key pk and numeric attribute version.
- Run the UpdateItem example with :old set to the current committed version and confirm the write succeeds and version increments.
- Run the same call with a stale :old value and confirm ConditionalCheckFailedException and that version is unchanged.
- Add ReturnValuesOnConditionCheckFailure to the request and confirm the returned image shows the current committed version even though the write was rejected.
- Check the AWS Service Quotas console for current expression length, item size, and TransactWriteItems limits before production sizing.
Rollback is not required for a failed conditional write because DynamoDB performs no mutation when the condition evaluates to false. For successful writes that must be undone, issue a compensating UpdateItem or DeleteItem with its own condition to avoid overwriting newer state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.