πŸ”§ Error Fixes
Β· 5 min read
Last updated on

MongoDB: E11000 Duplicate Key Error β€” How to Fix It


E11000 duplicate key error collection: mydb.users index: email_1 dup key: { email: "alice@example.com" }

This error means you’re trying to insert or update a document with a value that already exists in a field with a unique index. MongoDB rejects the operation at the storage engine level.

Why this happens

MongoDB enforces unique indexes strictly. When you create a unique index on a field (like email), any attempt to insert a document with a duplicate value in that field fails with error code 11000. This also applies to:

  • The default _id field (always has a unique index)
  • Compound unique indexes (the combination of fields must be unique)
  • Sparse unique indexes (multiple null values are allowed, but non-null values must be unique)

Common scenarios that trigger this error:

  1. Retrying a failed insert β€” The first attempt succeeded but your app didn’t get the acknowledgment (network timeout), so it retries and hits the duplicate
  2. Race conditions β€” Two requests try to create the same user simultaneously
  3. Data migration β€” Importing data that contains duplicates
  4. Forgotten index β€” A unique index exists on a field you didn’t expect
  5. Null values β€” Multiple documents with a missing field count as duplicate null values in a non-sparse unique index

Fix 1: Use upsert (insert or update)

The most common fix. If the document exists, update it. If not, insert it.

// Mongoose
await User.findOneAndUpdate(
  { email: 'alice@example.com' },
  { $set: { name: 'Alice', lastLogin: new Date() } },
  { upsert: true, new: true }
);

// Native driver
await db.collection('users').updateOne(
  { email: 'alice@example.com' },
  { $set: { name: 'Alice', lastLogin: new Date() } },
  { upsert: true }
);

When to use: Syncing data from external sources, handling form submissions where the user might already exist, any idempotent operation.

Fix 2: Catch and handle the error

Sometimes a duplicate is expected and you just want to handle it gracefully.

try {
  await db.collection('users').insertOne({ email, name, createdAt: new Date() });
} catch (err) {
  if (err.code === 11000) {
    // Duplicate β€” user already exists
    console.log(`User with email ${email} already exists`);
    // Optionally: return the existing document
    return await db.collection('users').findOne({ email });
  }
  throw err; // Re-throw unexpected errors
}

With Mongoose:

try {
  await User.create({ email, name });
} catch (err) {
  if (err.code === 11000) {
    const existing = await User.findOne({ email });
    return existing;
  }
  throw err;
}

Fix 3: Use bulkWrite with ordered: false

When inserting many documents and some might be duplicates, use unordered bulk operations. MongoDB will insert all valid documents and report errors for duplicates without stopping.

const docs = [
  { email: 'alice@example.com', name: 'Alice' },
  { email: 'bob@example.com', name: 'Bob' },
  { email: 'alice@example.com', name: 'Alice Duplicate' }, // Will fail
];

try {
  const result = await db.collection('users').bulkWrite(
    docs.map(doc => ({ insertOne: { document: doc } })),
    { ordered: false }
  );
  console.log(`Inserted: ${result.insertedCount}`);
} catch (err) {
  // err.result still contains info about successful inserts
  console.log(`Inserted: ${err.result.nInserted}, Errors: ${err.writeErrors.length}`);
}

Key: ordered: false means MongoDB continues processing remaining documents even when one fails. With ordered: true (default), it stops at the first error.

Fix 4: Remove the unwanted unique index

If the unique index was created by mistake or is no longer needed:

// List all indexes to find the problematic one
const indexes = await db.collection('users').getIndexes();
console.log(indexes);

// Drop the specific index
await db.collection('users').dropIndex('email_1');

With Mongoose, check your schema for unintended unique: true:

// ❌ This creates a unique index
const userSchema = new Schema({
  email: { type: String, unique: true },
  username: { type: String, unique: true }, // Did you mean this?
});

// βœ… Remove unique if not needed
const userSchema = new Schema({
  email: { type: String, unique: true },
  username: { type: String }, // Not unique
});

Important: After removing unique: true from a Mongoose schema, the index still exists in the database. You must drop it manually or use syncIndexes():

await User.syncIndexes(); // Drops indexes not in schema, creates missing ones

Fix 5: Handle null values in unique indexes

A non-sparse unique index treats missing fields as null. If multiple documents don’t have the indexed field, they all have null β€” which violates uniqueness.

// ❌ Two documents without email = two null values = duplicate key error
await db.collection('users').insertOne({ name: 'Alice' }); // email: null
await db.collection('users').insertOne({ name: 'Bob' });   // email: null β†’ E11000!

// βœ… Use a sparse index β€” ignores documents where the field is missing
await db.collection('users').createIndex(
  { email: 1 },
  { unique: true, sparse: true }
);

// βœ… Or use a partial filter expression (more flexible)
await db.collection('users').createIndex(
  { email: 1 },
  { unique: true, partialFilterExpression: { email: { $exists: true } } }
);

Fix 6: Handle race conditions

When two requests try to create the same document simultaneously, one will succeed and the other will get E11000. Use a retry pattern:

async function createOrGetUser(email, name) {
  try {
    return await db.collection('users').insertOne({ email, name });
  } catch (err) {
    if (err.code === 11000) {
      // Another request created it first β€” just fetch it
      return await db.collection('users').findOne({ email });
    }
    throw err;
  }
}

This pattern is safer than β€œcheck then insert” because it’s atomic β€” no gap between the check and the insert where another request could sneak in.

Debugging: Find which index caused the error

The error message tells you the index name:

E11000 duplicate key error collection: mydb.users index: email_1 dup key: { email: "alice@example.com" }
  • collection: mydb.users β€” the collection
  • index: email_1 β€” the index name (field email, ascending)
  • dup key: { email: "..." } β€” the duplicate value

If the index name is unexpected, list all indexes:

db.users.getIndexes()
// Returns: [{ key: { _id: 1 }, name: '_id_' }, { key: { email: 1 }, name: 'email_1', unique: true }]

FAQ

Can I have a unique index on a field that some documents don’t have?

Yes, use a sparse index or partial filter expression. Without these, all documents missing the field are treated as having null, and only one null is allowed in a unique index.

Does E11000 happen with updateOne too?

Yes. If you update a document’s email to a value that another document already has, you’ll get E11000. The uniqueness check applies to all write operations, not just inserts.

How do I handle this in Mongoose middleware?

userSchema.post('save', function(error, doc, next) {
  if (error.name === 'MongoServerError' && error.code === 11000) {
    next(new Error('A user with this email already exists'));
  } else {
    next(error);
  }
});

Does this error affect performance?

No. The unique check is done via the B-tree index lookup, which is O(log n). It’s the same cost as any indexed query. The error itself is cheap β€” MongoDB just rejects the write.