DEV Community

Cover image for Interview puzzle: "2 users buy last item, who win?"
Nam Mai
Nam Mai

Posted on

Interview puzzle: "2 users buy last item, who win?"

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 and False Conflict problems

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

Pessimistic Lock - Acquire Lock Before Update

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.

Optimistic Lock - Detect Conflicts At Update Time

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;
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

User B tries to save:

UPDATE product SET stock=9, version=6 WHERE id=1 AND version=5
Result: 0 rows affected
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Atomic Update

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.

Use Atomic Update to solve Lost Update and False Conflict

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)