DynamoDB Conditional Writes and Updates: Expression Examples

Written by Rafal Wilinski
Last updated: October 23rd, 2026
A DynamoDB conditional write runs a PutItem, UpdateItem or DeleteItem only when its ConditionExpression evaluates to true against the target item. DynamoDB evaluates the condition and performs the write atomically, so another writer cannot slip a change between the check and the mutation. Use attribute_not_exists for create-only puts, expected values for guarded updates, and ReturnValuesOnConditionCheckFailure to inspect conflicts without a separate read.
What a condition expression checks
The request's complete primary key selects one item. The condition checks that item's existing state, before the write. It does not search the table for matching records, and a condition on a put is not evaluated against the new object you are supplying.
For a composite-key table, both the partition and sort key identify the item. attribute_not_exists(pk) on a put prevents replacing that exact item; it does not prevent another item with the same partition key and a different sort key. Enforce uniqueness of a non-key field through a dedicated reservation item, usually written in a transaction.
A ConditionExpression differs from a read's FilterExpression. A filter removes data from a read result after DynamoDB has evaluated it. It gives a later write no protection. A write condition checks state again at the point when the mutation happens. AWS's condition expression examples show this distinction for puts, updates and deletes.
Conditional put: create an item only if it is absent
Without a condition, PutItem replaces an item with the same key and removes attributes omitted from the new item. This SDK v3 example uses an Orders table with a string partition key id:
An existing order-123 causes ConditionalCheckFailedException. Treat that as a duplicate or conflict, not permission to overwrite automatically. For idempotency, compare the existing request identity and payload before deciding that a duplicate means success.
If replacement is intended only for a particular revision, use a version equality condition instead and include the next version in the new item. The PutItem vs UpdateItem comparison explains replacement and partial-update behavior.
Conditional update: require the current state
This request changes an order from PENDING to PAID only if it is still pending and has the revision the caller read. The same request advances the version.
A missing item fails these comparisons. For an update with no other state condition, add attribute_exists(id) when you want to prevent the default upsert behavior.
The version protocol is covered in optimistic locking. For a counter or inventory value, it can be simpler to condition on the business rule itself, such as #stock >= :quantity, and subtract the quantity in the same update.
Comparisons, functions and expression placeholders
Attribute names use aliases beginning with #; values use placeholders beginning with :. This avoids reserved-word conflicts and keeps dynamic values separate from expression syntax. Use aliases for names containing dots or hyphens, too: a literal attribute called customer.name differs from a nested map path.
| Purpose | ConditionExpression example | Meaning |
|---|---|---|
| Create only | attribute_not_exists(#id) | No item at this complete key |
| Update only | attribute_exists(#id) | An item exists at this key |
| Expected state | #status = :pending | Stored value equals the expected value |
| Reject one value | #status <> :closed | Stored value differs from the supplied value |
| Sufficient balance | #balance >= :amount | Balance is at least the debit |
| Inclusive range | #total BETWEEN :low AND :high | Value is within both endpoints |
| Allowed states | #status IN (:pending, :held) | Value matches one member of the supplied list |
| Prefix | begins_with(#reference, :prefix) | String or binary value starts with the prefix |
| Membership | contains(#tags, :tag) | Set or list contains an element; also supports string substring checks |
| Type check | attribute_type(#total, :numberType) | Value has the supplied DynamoDB type, such as "N" |
| Length or size | size(#reference) = :length | String length matches; other supported types have their own size rules |
Combine predicates with AND, OR and NOT, using parentheses to make grouping explicit. For example, attribute_not_exists(#expires) OR #expires > :now admits items with no expiration or a future expiration. Use the same data type on both sides of a comparison; the number 120 and the string "120" are different values.
attribute_exists checks presence, not truthiness. An attribute explicitly stored with DynamoDB's NULL type still exists. if_not_exists is an update-expression function, not a substitute for attribute_not_exists in a condition. See AWS's operator and function reference.
Conditions on deletes
DeleteItem can guard against removing an item that has changed since it was reviewed. Here, deletion is allowed only for the expected canceled order revision:
ReturnValues: "ALL_OLD" returns the item successfully deleted. ReturnValuesOnConditionCheckFailure: "ALL_OLD" instead requests the item when the condition rejects deletion. An unconditional delete of an absent item normally succeeds; add an existence condition if absence should be reported as a conflict.
Inspect a failed condition without another read
A failed condition leaves the item unchanged and returns ConditionalCheckFailedException. Requesting ALL_OLD returns the pre-write item when one exists. It does not add read-capacity charges, and it avoids a diagnostic GetItem that could observe a later state.
The low-level boto3 client exposes the error's Item in DynamoDB JSON. Deserialize it explicitly:
A failed condition is not usually transient. Retrying the same stale version indefinitely will not repair the conflict. Reload or merge an edit, reject an invalid state transition, or check whether the earlier request already achieved the intended result. Use backoff for throttling and transient service failures separately.
The conditional request still consumes capacity even when rejected. A successful returned image and a failure image are different response paths; do not expect ReturnValues alone to populate a failed-condition exception. AWS documents both in working with items.
Updating a collection of items conditionally
DynamoDB does not offer an unrestricted multi-row UPDATE ... WHERE ... operation. PartiQL supports conditional item updates, but each update must identify one item by key. To change a collection:
- Query the relevant key range, or scan if the access pattern has no usable key.
- Follow pagination until the required data has been evaluated.
- Submit one conditional update per item, with bounded concurrency.
- Record successes, conflicts and failed requests so the job can resume safely.
A filter from step one is not sufficient: items may change between the read and the write. Repeat the important predicate in each write condition. BatchWriteItem cannot carry update actions or conditions. For related changes that must commit together, use transactions; for independent updates, use per-item requests or PartiQL batches and inspect every result.
Dynobase's bulk editor helps inspect and edit selected records. For a migration that depends on concurrency rules, make those rules explicit in the application requests rather than assuming a visual selection is a lock.
Changing an ID is a key migration
Suppose IDs 89 and 42 should become updated_89 and updated_42. If id is the table's primary key, UpdateItem cannot change it. Putting an item with the new ID creates another item and leaves the old key in place.
For each source record, read its complete contents and expected version. In a transaction, put the copied item under the new key with a create-only condition and delete the old key with the expected-version condition. That prevents overwriting an existing destination and prevents deleting a source that changed after the read. Resolve collisions and retries explicitly before running the next group.
If the field is an ordinary non-key attribute, a conditional UpdateItem is enough. Compute the new value in your application; condition on the old value so a concurrent edit is not lost. This preserves the useful transformation workflow without confusing duplication with renaming a primary key.
Performance considerations and use cases
Conditions are useful for create-only writes, safe state transitions, optimistic concurrency, guarded deletes, idempotency and lease ownership. They operate on one target item; choose a transaction when the invariant spans more than one.
Keep conflict handling visible in metrics. A high rate of failed version checks can indicate a hot item, a stale cache or a background writer bypassing the version protocol. A partial update can save network payload, but its capacity charge still depends on the full item's size. Bound bulk-job concurrency and preserve the failed keys so retrying does not require rescanning everything.
Frequently Asked Questions
How do I make a DynamoDB conditional put?
Can a DynamoDB condition check another item?
Should I retry ConditionalCheckFailedException?
Can BatchWriteItem perform conditional updates?
Does attribute_not_exists enforce uniqueness across a partition?
Dynobase is a Professional GUI Client for DynamoDB
Start your 7-day free trial today