The Error
You insert or update a document and MongoDB fires back:
MongoServerError: E11000 duplicate key error collection: mydb.users index: email_1 dup key: { email: "john@example.com" }
The message is surprisingly readable. It tells you the collection (mydb.users), the index that triggered it (email_1), and the exact duplicate value ("john@example.com"). MongoDB has a unique index on that field โ and the value is already taken.
Root Cause
Unique indexes guarantee no two documents share the same value for an indexed field. E11000 shows up in four common situations:
- Inserting a document whose field value already exists in the collection.
- An
upsertthat mistakenly creates a new document instead of updating the existing one. - Adding a unique index to a collection that already has duplicate values โ MongoDB rejects the index creation entirely.
- A race condition: two concurrent requests insert the same value within milliseconds of each other.
Fix 1: Find and Remove the Duplicates
Start by understanding what's already in the collection. Don't guess โ query it:
// Find all documents with the duplicate value
db.users.find({ email: "john@example.com" })
// Count duplicates across the whole collection
db.users.aggregate([
{ $group: { _id: "$email", count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } }
])
Once you know which are duplicates, keep the one you want and delete the rest:
// Delete the older document by its _id
db.users.deleteOne({ _id: ObjectId("64a1b2c3d4e5f6a7b8c9d0e1") })
Fix 2: Use upsert Correctly
When upsert: true is set, MongoDB looks for a document matching your filter. No match means it inserts a new one. That's where things break โ if the filter doesn't include the unique field, MongoDB creates a duplicate instead of updating the existing document:
// Wrong โ filter doesn't target the unique field
db.users.updateOne(
{ name: "John" },
{ $set: { email: "john@example.com" } },
{ upsert: true }
)
// Correct โ filter on the unique field
db.users.updateOne(
{ email: "john@example.com" },
{ $set: { name: "John" } },
{ upsert: true }
)
Fix 3: Drop or Rebuild the Unique Index
Sometimes the unique constraint itself is the problem โ your schema changed, or you never needed it. Remove it:
// List indexes to find the right name
db.users.getIndexes()
// Drop the unique index
db.users.dropIndex("email_1")
// Recreate without unique if needed
db.users.createIndex({ email: 1 })
Want to keep uniqueness but the existing data has duplicates? Clean first, then rebuild:
// After removing duplicates, recreate the unique index
db.users.createIndex({ email: 1 }, { unique: true })
Fix 4: Catch Error Code 11000 in Application Code
User registration, form submissions, API endpoints โ wherever users supply data, duplicates are a normal occurrence. Catch the error and return a clear message instead of letting the exception bubble up:
// Node.js / Mongoose
try {
await User.create({ email: "john@example.com" });
} catch (err) {
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
return res.status(409).json({ error: `${field} already exists` });
}
throw err;
}
# Python / PyMongo
from pymongo.errors import DuplicateKeyError
try:
collection.insert_one({ "email": "john@example.com" })
except DuplicateKeyError as e:
print(f"Duplicate entry: {e.details['keyValue']}")
Fix 5: Race Condition โ Concurrent Inserts
Two requests arrive within the same millisecond. Both check for an existing document, find nothing, and try to insert. One wins. The other gets E11000.
The unique index is working correctly โ it's protecting your data. Handle the error at the application layer (catch code 11000 as shown above), or switch to an atomic findOneAndUpdate with upsert:
db.users.findOneAndUpdate(
{ email: "john@example.com" },
{ $setOnInsert: { email: "john@example.com", createdAt: new Date() } },
{ upsert: true, returnDocument: "after" }
)
MongoDB executes this as a single atomic operation. It either finds the existing document or inserts a new one โ never both.
Prevention
- Query before bulk imports โ run the aggregation duplicate-check before loading large datasets, not after the damage is done.
- Use
ordered: falsefor bulk inserts โ MongoDB skips duplicates and continues processing the rest instead of stopping at the first error:
db.users.insertMany(docs, { ordered: false })
- **Always catch error code 11000** โ in every route or service that writes to a collection with unique indexes.
- **Clean data before adding unique indexes** โ run the duplicate check, remove the extras, then create the index.
## Verify the Fix
Run the duplicate check again. The result should be empty:
// Should return nothing db.users.aggregate([ { $group: { _id: "$email", count: { $sum: 1 } } }, { $match: { count: { $gt: 1 } } } ])
// Retry the insert โ should succeed now db.users.insertOne({ email: "john@example.com", name: "John" }) // { acknowledged: true, insertedId: ObjectId("...") }

