Conflicts
Stop two people booking the same 3pm slot, taking the same username or buying the last item in stock. Each protection is a precondition: an option on a call you already make.
A 409 conflict means someone else got there
first. Catch it and tell the person the slot, stock or edit was taken. Do not hide
the conflict behind an automatic retry.
Uniqueness with keyed creates
Derive the key from the thing that must be unique. The second writer gets a 409; your
own retry returns your record (existing: true); deleting the record frees
the key. Keys are 1–120 characters of letters, numbers and : _ . @ / -.
await bookings.create({ who: email }, { key: "slot:2026-07-15T15:00" })
Limited stock: N units anyone can buy
Claim units with keyed creates, and on conflict try the next unit. When every unit is taken, the item is sold out. This holds under every safety rule, because no rule lets a person write another person's records.
await orders.create({ item: 42 }, { key: "unit:item42:1" }) // then :2 … :N
Atomic counters
Atomic ops run against the current state on the server. An op that would pass the floor or ceiling returns 409 and leaves the record untouched.
await products.update(id, { stock: { decrement: 1, floor: 0 } })
An object in a patch is an atomic op only when its keys are
increment or decrement (plus an optional floor or
ceiling) and every value is a number. Anything else is stored as plain
data.
Shared editing with versions
Pass back the version you read. A stale save gets a 409 and leaves the other person's edit in place. Re-read, reapply your change, and retry.
await pages.update(id, { body }, { ifVersion: page.version })
One per person, limited supply
A booking that must be unique and capped per person needs two keyed creates: claim the resource first, then the per-person cap. If the second 409s, delete the first, which frees its key:
const slot = await bookings.create({ when }, { key: "slot:" + when })
try {
await quotas.create({ ref: slot.id }, { key: "user-day:" + user.userId + ":" + day })
} catch (e) {
if (e.code === "conflict") { await bookings.delete(slot.id) } // give the slot back
throw e
}
Don't check-then-create, and don't compute counters in the client. Both break when two people act at once.
What happens if…
| If… | What happens | What you do |
|---|---|---|
| Two people create with the same key | The first wins; the second gets 409 conflict. Keys are unique across the whole collection, whoever wrote the record | Tell the second person it was taken |
| Your own create with a key is retried | You get your existing record back with existing: true; nothing is created twice | Nothing |
| A create without a key is retried | The retry can make a second record | Use a key for anything that must happen once |
| The record holding a key is deleted | The key is free again | Nothing |
| A counter op would pass its floor or ceiling | 409 conflict; the record is untouched. A field that was never written counts from 0 | Tell the person it ran out |
| A save carries a version someone else already moved on from | 409 conflict; the other person's edit stays | Re-read, reapply, and save again |
| Two apps use the same key | No conflict; keys are per collection, inside one app | Nothing |
Thanks for telling us.
Thanks for telling us.
Last updated