skip to content

When would you use findOneAndUpdate instead of updateOne in MongoDB?

level: middleimportance: should knowfreq 55%

answer

  1. One returns counts, one returns a document
  2. Think about what you need back
  3. Which one can choose among several matches?
  4. A queue worker claiming an item
  5. Before or after the change

basics

~20 s

Use findOneAndUpdate when you need the document back as part of the write — either its pre-update state or the post-update state via returnDocument: "after" — or when a sort must decide which single document is modified. updateOne only returns counts.

solid answer

~50 s

`updateOne` reports `matchedCount` and `modifiedCount`; it never hands you the document. `findOneAndUpdate` selects one document, modifies it, and returns it in the same round trip — by default the version **before** the change, or the version after it when you pass `returnDocument: "after"`. Two capabilities follow. First, you get the resulting values without a separate read, so a counter incremented with `$inc` can return its new value with no risk that another client's write lands in between. Second, the `sort` option chooses *which* document is modified when several match — the highest priority, the oldest queued item — which `updateOne` cannot express. That makes it the standard way to claim work: filter on `status: "pending"`, set it to `"running"`, and two workers can never claim the same document. It also accepts `projection`, `upsert` and `arrayFilters`, and only ever touches one document.

code

javascript · 6 lines
javascript
const job = db.jobs.findOneAndUpdate(
  { status: "pending" },
  { $set: { status: "running", startedAt: new Date() } },
  { sort: { priority: -1, createdAt: 1 }, returnDocument: "after" }
)
// job is null when nothing was pending

go deeper

for a junior

Know that updateOne gives you counts while findOneAndUpdate hands back the document, and that returnDocument controls whether that is the before or after state.

for a middle

Explain the sort option, the null return when nothing matches, how projection and upsert combine with it, and why a read-back after updateOne can observe another client's write.

for a senior

Present the claim pattern end to end: index support for filter plus sort, contention as workers scale, and what happens to a claimed document when a worker dies mid-run.

for a principal

Weigh a database-backed queue built on this against a dedicated queue system — throughput ceilings from hot-document contention, visibility timeouts, and where the operational burden lands.

## Two APIs, one difference that matters Both `updateOne` and `findOneAndUpdate` modify a single document that a filter selects. The difference is what comes back and how the target is chosen. `updateOne(filter, update, options)` returns a small result object: `matchedCount`, `modifiedCount`, and — for an upsert — `upsertedId`. If the application needs the document's contents afterwards, it must issue a separate `find`, and between the write and that read another client may have changed the document again. The value you read back is then not necessarily the value your write produced. `findOneAndUpdate(filter, update, options)` returns the document itself. The select-and-modify is a single server-side operation on one document, so the document you are handed is exactly the one your update touched. ## returnDocument By default `findOneAndUpdate` returns the document **as it was before** the update. Passing `returnDocument: "after"` returns the post-update state instead. (The legacy mongo shell spelled this `returnNewDocument: true`; current drivers and mongosh use `returnDocument` with `"before"` or `"after"`.) Which you want depends on the use case: an audit trail usually wants the previous values, while a counter or a claimed job wants the new ones. If no document matches and `upsert` is not requested, the command returns null rather than raising an error. ## The sort option This is the capability people forget. `updateOne` gives no control over which of several matching documents is modified. `findOneAndUpdate` accepts `sort`, so you can say "of all pending jobs, take the highest priority, oldest first": ```js const job = db.jobs.findOneAndUpdate( { status: "pending" }, { $set: { status: "running", startedAt: new Date() } }, { sort: { priority: -1, createdAt: 1 }, returnDocument: "after" } ) ``` Because the find and the modify happen as one operation on that document, two workers running this concurrently cannot both receive the same job: whichever executes second no longer sees it as `pending`. This is the canonical job-claim pattern, and it is why the operation exists. As with any query that sorts, the sort should be supported by an index or it will be doing work in memory on every claim. ## Getting a computed value back The second classic use is a value the server computes: ```js const c = db.counters.findOneAndUpdate( { _id: "invoice" }, { $inc: { seq: 1 } }, { returnDocument: "after", upsert: true } ) // c.seq is the number this caller owns ``` Each caller receives a distinct number. Doing the same with `updateOne` followed by `find` would let a concurrent increment slip in between, so two callers could read the same value. ## Other options `projection` limits the fields of the returned document — useful when the document is large but you only need a field or two. `upsert: true` behaves as it does elsewhere, and combined with `returnDocument: "after"` it gives you the created document. `arrayFilters` works exactly as it does for `updateOne`, so filtered positional updates are available. There are sibling commands with the same shape: `findOneAndReplace` and `findOneAndDelete`, the latter being how you claim-and-remove from a queue collection. ## Costs and limits `findOneAndUpdate` touches exactly one document — there is no "findManyAndUpdate". Returning the document means shipping it back to the client, which for a large document is real bandwidth you did not spend with `updateOne`; a `projection` trims that. And because a claim pattern concentrates many clients on the same small set of documents, contention on the hottest documents is the thing to watch as worker count grows: every claimer is competing to modify the head of the same queue. ## Choosing between them Default to `updateOne` — it is cheaper and says exactly what it does. Reach for `findOneAndUpdate` when the response must carry the document, when a sort must pick the target, or when the read-back must be free of interference from concurrent writers. Reach for `updateMany` when the change applies to a set of documents; `findOneAndUpdate` is not the tool for bulk changes.

  • By default, does findOneAndUpdate return the document before or after the update?
    Before. The default is the pre-update state; you get the post-update document by passing `returnDocument: "after"` (the legacy mongo shell spelled it `returnNewDocument: true`). Reaching for the wrong one is a common bug — a counter read back with the default returns the value the caller *did not* get.
  • Why can two workers not claim the same job with findOneAndUpdate?
    Because selecting and modifying that document happen as one operation. The first claim flips `status` from `"pending"` to `"running"`, so the second worker's filter no longer matches it and the server hands that worker a different document — or null. Splitting this into a find followed by an updateOne reopens the window between the two.
  • What does the sort option add, and what should you check before relying on it?
    `sort` decides which of several matching documents is modified — highest priority, oldest created, whatever you order by. Check that an index supports the filter-plus-sort combination: without one, every claim performs an in-memory sort of the matching set, which degrades badly as the backlog grows.

saying these in an interview costs you the question

  • Thinks updateOne returns the modified document
  • Assumes findOneAndUpdate returns the post-update document by default
  • Believes a find followed by updateOne is equivalent
  • Expects findOneAndUpdate to modify several matching documents
  • Uses it for large documents without a projection and calls the bandwidth free

context