What is optimistic locking in DynamoDB and how do conditional writes work?
Learn how DynamoDB optimistic locking uses a version attribute and conditional writes to prevent lost updates, with code, common mistakes and interview Q&A.
Expected Interview Answer
Optimistic locking in DynamoDB is a concurrency strategy where you assume writes rarely conflict, so instead of locking an item you attach a version attribute and only allow an update to succeed if the version still matches what you last read — enforced through a conditional write.
You read an item together with its version number, then issue an UpdateItem or PutItem carrying a ConditionExpression like 'version = :expected'. DynamoDB evaluates the condition atomically against the current item; if another client already bumped the version, the condition fails with a ConditionalCheckFailedException and your write is rejected so you can re-read and retry. Conditional writes are the underlying primitive: any write can carry a ConditionExpression (attribute_exists, attribute_not_exists, comparisons) that must be true for the operation to apply, giving you check-and-set semantics without a separate lock table.
- Prevents lost updates from concurrent writers
- No locks held, so no blocking or deadlocks
- Atomic check-and-set within a single item
- Works naturally with DynamoDB's high concurrency
- Failed conditions don't consume a write, only a small read cost
AI Mentor Explanation
Two scorers update the same official scoreboard after a wicket falls. Each writes the version number of the scorecard they last saw. When one submits, the match referee only accepts the update if that version still matches the current one; if the other scorer already recorded the wicket and bumped the version, the second submission is rejected and that scorer must re-read the latest score before trying again — nobody overwrites a change they never saw.
Step-by-Step Explanation
Step 1
Read the item with its version
Fetch the item and note the current value of a version attribute (e.g. a numeric 'version' field).
Step 2
Make your changes locally
Compute the new attribute values you intend to write while remembering the version you read.
Step 3
Write with a ConditionExpression
Issue UpdateItem/PutItem that increments the version and includes a condition 'version = :expectedVersion'.
Step 4
Let DynamoDB evaluate atomically
DynamoDB checks the condition against the current item as a single atomic operation before applying the write.
Step 5
Handle ConditionalCheckFailedException
If the condition fails, another writer won the race — re-read the latest item and retry the update from step one.
What Interviewer Expects
- Understands optimistic vs pessimistic concurrency control
- Knows DynamoDB has no server-side locks and relies on conditional writes
- Can describe the version-attribute pattern and increment on each write
- Names ConditionalCheckFailedException and the retry loop
- Mentions ConditionExpression primitives like attribute_exists / comparisons
Common Mistakes
- Thinking DynamoDB acquires a row/item lock like a relational database
- Forgetting to increment the version attribute in the same update
- Not handling ConditionalCheckFailedException and silently losing updates
- Assuming a plain UpdateItem is safe under concurrency without a condition
- Confusing conditional writes with transactions (TransactWriteItems)
Best Answer (HR Friendly)
“Optimistic locking is a way to stop two people from overwriting each other's changes at the same time. You tag each record with a version number and only allow a save if the version still matches what you last saw; if someone else changed it first, your save is rejected and you reload and try again.”
Code Example
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient, UpdateCommand } from '@aws-sdk/lib-dynamodb'
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}))
async function updateProfile(userId, newEmail, expectedVersion) {
try {
await ddb.send(new UpdateCommand({
TableName: 'Users',
Key: { userId },
UpdateExpression: 'SET email = :email, version = :next',
ConditionExpression: 'version = :expected',
ExpressionAttributeValues: {
':email': newEmail,
':next': expectedVersion + 1,
':expected': expectedVersion,
},
}))
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
// Someone else updated first: re-read and retry
throw new Error('Version conflict, please retry')
}
throw err
}
}Follow-up Questions
- How does optimistic locking differ from pessimistic locking?
- When would you use TransactWriteItems instead of a single conditional write?
- How does the DynamoDBMapper @DynamoDBVersionAttribute implement this pattern?
- What happens to consumed capacity when a conditional write fails?
- How would you implement a retry-with-backoff loop around a version conflict?
MCQ Practice
1. What exception does DynamoDB throw when a conditional write's condition is not met?
When a ConditionExpression evaluates to false, DynamoDB rejects the write with ConditionalCheckFailedException.
2. Optimistic locking in DynamoDB is typically implemented using?
You store a version attribute and require 'version = :expected' in the write's condition, incrementing it on success.
3. Why is it called 'optimistic' locking?
Optimistic concurrency assumes writes rarely collide, so no lock is held; the version is only validated at commit time.
Flash Cards
What primitive enables optimistic locking in DynamoDB? — Conditional writes — a ConditionExpression on UpdateItem/PutItem that must be true for the write to apply.
What error signals a version conflict? — ConditionalCheckFailedException — another writer changed the item first; re-read and retry.
Does DynamoDB hold locks during optimistic locking? — No. No lock is held; concurrency safety comes from the atomic condition check at write time.
What must you do to the version on every successful write? — Increment it (SET version = :next) so subsequent stale writes fail their condition.