updateOne and updateMany in MongoDB

Learn to update MongoDB documents with updateOne and updateMany. This MongoDB tutorial covers syntax, examples, and best practices for targeted updates.

Focus: updateOne and updateMany MongoDB

Sponsored

You've mastered inserting data into MongoDB, but what happens when that data changes? Your users update their profiles, prices fluctuate, and order statuses shift. Without a solid update strategy, you're left rewriting whole documents, risking data loss and eating up bandwidth. That's where updateOne and updateMany come in — your precise, surgical tools for modifying exactly what needs to change, without touching the rest. Let's master them.

The problem this lesson solves

Imagine your e-commerce database has a products collection. A supplier raises the price of a single item. With what you know so far, you might re-insert the whole document — but that's clunky and prone to error. Worse, if the collection has a million products and you need to bump every price by 10 percent, you don't want to replace each document manually. You need a way to update one or many documents efficiently, targeting only the fields that changed. The old update() method is deprecated and confusing, and trying to use save() on a document you've fetched is a recipe for race conditions. updateOne and updateMany solve this cleanly, giving you atomic, efficient updates with fine-grained control.

Core concept / mental model

Think of a MongoDB collection as a drawer full of index cards (documents). updateOne is like pulling exactly one card that matches your criteria, writing a correction on it, and sliding it back. updateMany is like taking a red pen and marking the same correction on every card that matches. The key is that you're not crumpling up the card and writing a new one — you're only altering the parts you specify.

In MongoDB terms, each update operation takes two main arguments: a filter (which documents to affect) and an update (what to change). The filter works just like the query you use with find(). The update uses operators like $set to assign new values, $inc to increment numbers, or $rename to change field names. You can also add new fields or remove them with $unset. This is the heart of the mental model: filter selects, update transforms, and the method determines scope.

How it works step by step

Let's break down the mechanics of an update operation.

  1. Choose your method: updateOne for the first match, updateMany for all matches.
  2. Write the filter: This is a document that specifies which documents to affect. For example, { "status": "pending" } matches every document with that status.
  3. Define the update: Use update operators to modify fields. The most common is $set, which sets a field to a specific value.
  4. Execute: Run the method on the collection.
  5. Check the result: The method returns a result object with matchedCount (how many documents matched the filter) and modifiedCount (how many were actually changed).

Why does order matter? MongoDB evaluates the filter first, then applies the update to all matched documents. With updateOne, if multiple documents match, only the first (in natural order) is updated — the rest are ignored. With updateMany, every match gets updated, but all updates are applied atomically per document, so no partial updates leak.

Upsert is a powerful addition: set { upsert: true } and MongoDB will insert a new document if no match is found. That's perfect for counters or settings where you want to ensure a record exists.

Hands-on walkthrough

Let's get our hands dirty. First, insert some sample data into a users collection.

// Insert sample users
db.users.insertMany([
  { name: "Alice", age: 30, status: "active", score: 85 },
  { name: "Bob", age: 25, status: "inactive", score: 60 },
  { name: "Carol", age: 35, status: "active", score: 72 },
  { name: "Dave", age: 28, status: "pending", score: 55 }
]);

// Output: acknowledged: true, insertedIds: ...

Now, let's update a single user's status. We'll change Alice's status to "inactive".

db.users.updateOne(
  { name: "Alice" },        // filter
  { $set: { status: "inactive" } }  // update
);

// Output:
// { acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }

Notice matchedCount: 1 and modifiedCount: 1. If we run the same update again, modifiedCount becomes 0 because the document already has that value — MongoDB optimizes to avoid unnecessary writes.

Now update all active users to add a loyaltyLevel field and increment their score by 5.

db.users.updateMany(
  { status: "active" },
  { $set: { loyaltyLevel: "gold" }, $inc: { score: 5 } }
);

// Output:
// { acknowledged: true, matchedCount: 2, modifiedCount: 2, upsertedCount: 0 }

Both Alice and Carol (both active) got the new field and a score boost.

Let's also try an upsert. Update a user with a specific email; if they don't exist, create them.

db.users.updateOne(
  { email: "eve@example.com" },
  { $set: { name: "Eve", status: "active" } },
  { upsert: true }
);

// Output:
// { acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedCount: 1 }

Now the collection has a new document for Eve, proving the upsert worked.

Pro tip: Use $set to add new fields without overwriting existing ones. It's idempotent and safe — running it twice doesn't break anything.

Compare options / when to choose what

Both methods have their place. Here's a quick comparison:

Scenario updateOne updateMany
Update a single document ✅ Best choice ❌ Overkill
Update all documents matching a filter ❌ Only updates first ✅ Best choice
Performance on large collections Fast (indexed filter) Fast (streams operations)
Risk of unintended changes Low High — ensure filter is precise
Upsert behavior Supported Supported

When to use which? Use updateOne when you have a unique filter (like _id) and need to update exactly one record — a user profile, an order status. Use updateMany for bulk operations like applying a discount to all products in a category, marking all notifications as read, or updating timestamps for a batch of sessions. Always double-check your filter to avoid wiping out data unintentionally.

Variations: You can also replace an entire document with replaceOne(), but that's a different operation entirely and risks losing fields. For partial updates, stick to updateOne/updateMany. There's also findAndModify() for when you need the updated document returned in the same call.

Troubleshooting & edge cases

  • Wrong result counts: If matchedCount is 0, no documents matched your filter — check field names and data types. For example, matching on a numeric field with a string won't work.
  • No changes despite match: modifiedCount being 0 means the document already has the value you're setting. That's not an error.
  • Unexpected updates: With updateMany, one wrong filter can change thousands of documents. Always test with find() first to see what matches.
  • Using $ positional operator: If you have arrays and want to update a specific element, you need $ or $[]. Using $set without the positional operator won't update array elements as expected.
  • Race conditions: If multiple processes update the same document, the last write wins. Use atomic operators like $inc to avoid lost updates.

Blockquote: When in doubt, run a find() with the same filter to preview which documents will be affected. It's cheap insurance.

What you learned & what's next

You now know how to update documents with updateOne and updateMany — the surgical and the bulk approaches. You learned to write filters, use $set, $inc, and $upsert, and read result objects to confirm your changes. You also learned when to choose each method and how to troubleshoot common pitfalls.

Ready for the next step? In the next lesson, you'll dive into deleting documents with deleteOne and deleteMany, completing your CRUD toolkit. With insert, update, and delete under your belt, you'll be able to manage data through its entire lifecycle in MongoDB.

Now open your MongoDB shell, load some sample data, and practice updating a single document by _id, then update a whole collection with a condition. Experiment with upserts and see how matchedCount and modifiedCount behave. The more you practice, the more natural it becomes.

Practice recap

Insert a sample collection, then run an updateMany to add a lastSeen timestamp to all documents with status: "active". Then use updateOne with upsert: true to create a settings document if it doesn't exist. Watch how matchedCount and modifiedCount change with each run — especially when you run the same update twice.

Common mistakes

  • Forgetting to include an update operator like $set — using a plain object replaces the whole document, erasing unmatched fields.
  • Using updateMany when you only meant to affect one document — always verify the filter scope first with find().
  • Ignoring the result object — matchedCount and modifiedCount are key to confirming your update actually applied.
  • Assuming $inc works on strings — it only operates on numeric fields; non-numeric values cause an error.
  • Using the $ positional operator without an array context — you'll get a 'Cannot apply positional operator' error.

Variations

  1. replaceOne(): Replaces an entire document with new content — use when you want to reset all fields, not partial updates.
  2. findOneAndUpdate(): Atomically updates and returns the modified document — handy for counters or audit logs.
  3. Bulk-write operations with bulkWrite(): For mixed create/update/delete operations in a single batch, improving performance.

Real-world use cases

  • Price updates: applying a discount to all products in a specific category using updateMany.
  • User profile changes: updating a single user's email or phone number with updateOne by _id.
  • Session maintenance: marking all user sessions as expired with an updateMany when a system-wide reset occurs.

Key takeaways

  • updateOne modifies the first document matching your filter; updateMany modifies all matches.
  • Always use update operators like $set, $inc, or $unset to avoid wiping out entire documents.
  • Check matchedCount and modifiedCount in the result to verify updates went through as expected.
  • Upsert (upsert: true) inserts a new document if no match exists, perfect for ensuring a record is present.
  • Filters work identically to find() queries — test with find() first to avoid accidental mass updates.
  • Use updateMany for bulk operations and updateOne for precise, single-record changes.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.