Skip to content

Concurrency

Race Conditions

Read-modify-write in application code versus an atomic operator on the server.

AdvancedAbout 6 minutes

A race is check-then-act in Node: two requests both see a state that is already gone by the time they write. Atomicity of updateOne does not help if the decision happened on a stale findOne.

const p = await db.products.findOne({ name: "Mechanical Keyboard" })
if (p.stock >= 1) {
  await db.products.updateOne(
    { name: "Mechanical Keyboard" },
    { $set: { stock: p.stock - 1 } }
  )
}
Two checkouts, one keyboard. Both see stock 1.
const r = await db.products.updateOne(
  { name: "Mechanical Keyboard", stock: { $gte: 1 } },
  { $inc: { stock: -1 } }
)
if (r.matchedCount === 0) { /* sold out */ }
The decision belongs in the filter. One winner.
RacePut the check in the write
Ship twice{ status: "pending" }, $set: shipped — second matchedCount 0
Duplicate emailunique index; handle 11000 — not find then insert
Double redeem{ coupon: id, used: false }, $set: { used: true }
Stockstock: { $gte: n }, $inc: -n

matchedCount === 0 is the conflict signal for compare-and-swap without a version field: you named the expected state in the filter. Unique indexes are the insert-side version of the same idea. Transactions do not fix a find-then-set you left outside the session. The previous module is $inc; this lesson is stop reading to decide.

Interview question

How do you avoid a check-then-act race in MongoDB?

Think about it first.