Concurrency control
Two valid requests can still produce an invalid result
Concurrency control protects the rule shared requests depend on. Start with that rule, then choose conditional writes, locks or transaction isolation to enforce it.
In this article
Write down what must remain true
A booking service has one remaining place. Two requests each read availability as one and both create a booking. Each request followed the same reasonable sequence, but together they oversold the session.
The important rule is that confirmed bookings must not exceed capacity. Checking availability before writing is insufficient unless the check and the effect are coordinated against competing requests.
Express that rule at the level where it can actually be enforced. A disabled submit button may reduce accidental clicks, but two users, retries and background jobs can still reach the server at the same time.
Choose a boundary that covers the decision
For a rule held in one row, an atomic conditional update may be enough. More complex rules can span several records and require a transaction strategy that protects the complete decision.
A version check is useful when editing a record based on a previously observed value. A lock can serialise access to a known resource. Stronger transaction isolation can protect broader interactions, with the retry behaviour required by the database.
Do not select a mechanism by habit. Explain which competing operation it prevents and which paths participate in the protocol.
- Booking requestValidate identity, quantity and operation key
- Protected decisionCheck capacity within the chosen concurrency boundary
- CommitSave reservation and related state together
- ResultConfirm success or explain the conflict
Keep external effects outside speculative work
A transaction may fail and need to run again. Sending an email or charging a provider inside a retried block can repeat an effect that the database cannot roll back.
Persist an intent to perform the external effect as part of the committed workflow, then deliver it through a mechanism with appropriate duplicate handling. The exact implementation depends on the system, but the retry boundary must remain explicit.
Also distinguish a conflict from a duplicate request. Two different users competing for one place is a capacity conflict. The same request arriving twice after a timeout is an idempotency problem. Many business operations need protection against both.
Design the losing request's experience
A failed conditional write should not become an unexplained server error. For a document edit, preserve the user's draft and show that a newer version exists. For a booking, explain that availability changed before confirmation.
Automatic retries are suitable only when recomputing the operation remains faithful to the user's intent. Silently overwriting another person's edit with the same old form values is not a safe retry.
Test the interleaving deliberately
Pause two requests after they read the same starting state, then release their writes. Assert the final invariant and each response.
Repeat through every writer, including imports and administrative tools. A concurrency scheme is only as strong as the paths that follow it. The result should remain valid even when one request is rejected, retried or interrupted after commit.
Primary sources
PostgreSQL: transaction isolationPostgreSQL: explicit lockingReferences checked 11 September 2026.