DynamoDB
data protection
conditional writes
database integrity
AWS best practices

How to prevent a DynamoDB item being overwritten if an entry already exists

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

By default, DynamoDB treats PutItem as an upsert. If another item already exists with the same primary key, the new write replaces it.

To make the operation behave like "insert only if missing", add a condition to the write itself. That keeps the check atomic and prevents two concurrent requests from overwriting each other.

Use ConditionExpression with PutItem

The standard solution is attribute_not_exists. DynamoDB evaluates the condition on the server and only performs the write when the key does not already exist. That means you do not need a separate existence check in application code.

javascript
1import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
3
4const client = new DynamoDBClient({});
5const docClient = DynamoDBDocumentClient.from(client);
6
7async function createUser(user) {
8  await docClient.send(
9    new PutCommand({
10      TableName: "Users",
11      Item: {
12        userId: user.userId,
13        email: user.email,
14        createdAt: new Date().toISOString()
15      },
16      ConditionExpression: "attribute_not_exists(userId)"
17    })
18  );
19}

If an item with the same userId is already present, DynamoDB throws ConditionalCheckFailedException and leaves the existing record untouched.

Be Explicit with Composite Keys

For a table with a partition key and a sort key, uniqueness is defined by the pair. It helps to write the condition in a way that makes that intent obvious to the next person reading the code.

javascript
1await docClient.send(
2  new PutCommand({
3    TableName: "Orders",
4    Item: {
5      customerId: "c-42",
6      orderId: "o-9001",
7      total: 149.99
8    },
9    ConditionExpression:
10      "attribute_not_exists(customerId) AND attribute_not_exists(orderId)"
11  })
12);

This makes the business rule visible in the request itself: only create the order if that exact key pair is new.

Handle Duplicate Attempts Cleanly

A failed conditional write is often a normal duplicate request, not a service outage. Treat it as a controlled application case instead of a generic infrastructure failure.

javascript
1async function safeCreateOrder(order) {
2  try {
3    await docClient.send(
4      new PutCommand({
5        TableName: "Orders",
6        Item: order,
7        ConditionExpression:
8          "attribute_not_exists(customerId) AND attribute_not_exists(orderId)"
9      })
10    );
11
12    return { created: true };
13  } catch (error) {
14    if (error.name === "ConditionalCheckFailedException") {
15      return { created: false, reason: "Order already exists" };
16    }
17
18    throw error;
19  }
20}

This is especially useful for APIs that need to return 409 Conflict, idempotent behavior, or a domain-specific message such as "user already exists."

Why Not Read First and Then Write

A common mistake is to call GetItem, decide the item is missing, and then send PutItem. That looks fine in single-user testing, but it is not safe under concurrency. Two requests can both read "missing" and both attempt the insert.

The conditional write closes that race because the check and the write happen together inside DynamoDB.

The result is simpler application code and stronger data integrity.

When to Consider Transactions

If the create operation must also update another item or another table, a single conditional PutItem may not be enough. In that case, move to TransactWriteItems and keep the same design principle: express correctness rules in DynamoDB conditions rather than relying on application-side sequencing.

Common Pitfalls

  • Doing a read-then-write flow and assuming it is safe under concurrency.
  • Referring to the wrong key attribute names inside ConditionExpression.
  • Treating ConditionalCheckFailedException like an unexpected crash instead of an expected duplicate case.
  • Forgetting that overwrites happen on PutItem by default when the primary key already exists.

One more practical pitfall is swallowing the duplicate exception and retrying blindly. If the request is genuinely a duplicate, retries do not help and only add noise to logs and metrics.

Summary

  • 'PutItem overwrites existing items unless you add a condition.'
  • Use attribute_not_exists to make a write succeed only for a new item.
  • Conditional writes are atomic, so they are safer than a separate read followed by a write.
  • Catch duplicate-write failures explicitly and return a meaningful result.
  • For composite keys, be clear about the full uniqueness rule in your condition.

Course illustration
Course illustration

All Rights Reserved.