Last week, a friend of mine asking me a classic question: "Imagine a flash sale where only one item is left. If two customers try to buy it at the exact same time, how does the system decide which customer gets the item?"
That question ignited a discussion in our group chat. There were many ideas, potential solutions, and even disagreements.
That debate is the reason I did my own research about popular problems of concurrency systems: Lost Update and False Conflict.
Lost Update: Who got the last item?
Generally, this programming problem can be explained as below:
Only 1 item left, two customers buy at same time, both got the success message.
It happens when there are two or more operations read the same data and then write back updates, causing one update to overwrite another. The earlier update is "lost" because it’s based on stale data.
Traditionally, we can prevent this problem by using locking mechanisms of database.
Pessimistic Lock
The database assumes that conflicts will happen, so it locks rows involved in the SQL statements (such as SELECT ... FOR UPDATE) to block other transactions from reading or writing until the lock releases on commit or rollback. That's pessimistic lock
It means when a user clicks the "Buy" button, the database literally locks that specific row. No one else can touch that row until the transaction is finished.
This approach guarantees data consistency but very slow. If 100 people try to buy a hot item, they form a queue, potentially crashing your server or timing out requests.
Optimistic Lock
Optimistic Lock take a better approach. It doesn't lock the row. Instead, adding a version number to the data.
When a user click the "Buy" button, the following scenario could happen:
Read stock (Stock: 10, Version: 5)
User tries to buy.
Database update query:
UPDATE inventory SET stock = 9, version = 6 WHERE id = 1 AND version = 5;
- If another user tried to buy at the same time, their query tries to find version = 5, but it's already 6. The update fails. Then our job is simply show him a "Sorry, stock changed" error.
False Conflict: Why I cannot buy this one?
Imagine 100 pairs of hot sneakers in stock. Two speedy shoppers (User A and User B) smash "Buy" at the same time:
Both read at the same time:
User A: "Stock=10"
User B: "Stock=10"
System do the math to update new stock = 9.
Update to database:
- User A saves first:
UPDATE product SET stock=9, version=6 WHERE id=1 AND version=5
Result: 1 row affected
New DB: stock=9, version=6
User B tries to save:
UPDATE product SET stock=9, version=6 WHERE id=1 AND version=5
Result: 0 rows affected
Why? DB says: "Version is now 6, but you gave me 5. Stale data—rejected!" (Throws OptimisticLockException)
There are still a lot of items but User B cannot buy any!
Atomic Update
Atomic Updates solve this by moving the math inside the database. Your app never reads stock or calculates - just sends a simple "subtract 1" instruction. The DB handles everything atomically.
The application never calculates the final number and the version. It simply sends an instruction.
- User A saves first:
UPDATE product SET stock = stock - 1, version = version + 1
WHERE id=1 AND version = 5
Database Logic: "Stock=10 → 9. Done!"
Result: 1 row affected
New DB: stock=9
- User B tries to save:
UPDATE product SET stock = stock - 1, version = version + 1
WHERE id=1 AND version = 5
Database Logic: "Stock=9 → 8. Done!"
Result: 1 row affected
New DB: stock=8
And to prevent overselling issue, you just simply add WHERE stock > 0 so the DB only subtracts if stock exists
UPDATE product SET stock = stock - 1, version = version + 1
WHERE id=1 AND version = 5 AND stock > 0
The Winner
Generally, Atomic Update stand out as the best solution. It eliminate Lost Updates by ensuring math happens correctly without races and avoid False Conflict by skipping locks.
But it's only the winner for a monolith database system and the operation is simple (in this post, just subtract the stock field).
What about cases where we need to update non-computable fields like email, name, or descriptions?
When we should use other locking mechanisms?
And, what about complex system like distributed databases and hundred of micro services?





Top comments (0)