CONCEPT
Who gets the last ticket?
Explore why a conditional UPDATE admits one competing reservation and refuses the other.
Two buyers can see one available ticket on the event page. That count is a snapshot. The reservation UPDATE decides whether each request succeeds.
Choose which buyer reaches the row first, then step through the requests. Either buyer can win; the guarantee is that both cannot hold the same last ticket. This illustration assumes both request one ticket and no unrelated transaction fails.
ILLUSTRATIVE WALKTHROUGH · STEP 1 OF 4
One ticket remains
- Available
- 1
- Reserved
- 0
- Row lock
- Unlocked
Both buyers request one ticket. The page may show one available ticket to both; neither request has reserved it yet.
Counters show committed database state.
One possible ordering at Read Committed isolation, with both transactions otherwise succeeding. This model does not run SQL. The linked Postgres test checks the implementation.
What the query guarantees
The availability condition and counter update are part of the same SQL statement. At Postgres's default Read Committed isolation, a competing UPDATE waits for the earlier transaction, then checks its condition against the updated row. After the first reservation commits, there is no remaining stock for the second request.
This is the actual repository method, extracted from source at build time:
async reserve(ticketTypeId, quantity, tx = db) {
assertPositiveInteger(quantity);
// UPDATE ticket_types SET quantity_reserved = quantity_reserved + $qty
// WHERE id = $id AND quantity_total - quantity_sold - quantity_reserved >= $qty
// RETURNING id
const rows = await tx
.update(ticketTypes)
.set({
quantityReserved: sql`${ticketTypes.quantityReserved} + ${quantity}`,
})
.where(
and(
eq(ticketTypes.id, ticketTypeId),
sql`${ticketTypes.quantityTotal} - ${ticketTypes.quantitySold} - ${ticketTypes.quantityReserved} >= ${quantity}`,
),
)
.returning({ id: ticketTypes.id });
return rows.length === 1;
}true means one row was updated. false means the reservation did not qualify.
The order service turns the refused hold into a sold-out error, inside the
transaction. No second order is created.
For the database behavior behind the illustration, read Postgres 17's Read Committed explanation.
What if the first transaction rolls back?
The provisional hold rolls back with the order and audit writes. The waiting request can then evaluate the row without that hold. The walkthrough above shows a successful commit, not this alternative. The written rule still applies: the database evaluates the current row, not the buyer's earlier page.
Follow the evidence
The real-Postgres concurrency test starts 50 competing one-ticket reservations against 20 available tickets. It checks that exactly 20 succeed and the stored availability never becomes negative. That checks the same guarantee at a larger scale; this browser model is an explanation, not a substitute for it.
Also see the repository integration tests, order transaction tests, and inventory decision.
Continue the Buy a ticket tour to see the reservation become a verified payment and issued tickets.
Source revision: 94a6d5c