Sign in to keep reading
“Dealing with Contention” is free with an account. Your progress and bookmarks follow you across devices.
Sign in to continueNEW: ML Mock & Coaching with a 20+ year experience coach. Save $20 on sessions for Premium.ML Mock & Coaching now available
NEW: ML Mock & Coaching with a 20+ year experience coach. Save $20 on sessions for Premium.ML Mock & Coaching now available
Handle concurrent modifications to shared resources using optimistic concurrency, pessimistic locking, atomic operations, and queue-based serialization.
“Dealing with Contention” is free with an account. Your progress and bookmarks follow you across devices.
Sign in to continuePicture two users withdrawing money from the same bank account at the exact same moment. Each one reads a balance of 100, each subtracts their amount, and each writes the result back. If User A withdraws 30 and User B withdraws 50, the correct final balance is 20. But because both read the same starting value, whichever write lands last simply overwrites the other. The balance ends up at either 70 or 50 -- never 20.
This is the lost update problem, and it is the most common form of a race condition. Every system that allows concurrent modifications to shared state -- a bank balance, a seat reservation, a chess board, an inventory count -- must deal with it explicitly.
The root cause is always the same: a read-modify-write cycle that is not atomic. The gap between reading and writing creates a window where another operation can slip in and invalidate the assumption your computation was based on. Every strategy in this article is a different way to close that gap.
Optimistic locking bets that conflicts are rare. Instead of preventing concurrent access, it detects conflicts at write time and retries when they happen.
The mechanism is simple: every row carries a version number (or a timestamp, or a hash). When you read the row, you note the version. When you write, you include the version in a conditional update:
UPDATE accounts
SET balance = 70, version = 8
WHERE id = 123 AND version = 7;
If another transaction modified the row first, the version is no longer 7, the WHERE clause matches zero rows, and your update silently does nothing. Your application checks the affected-row count, sees zero, re-reads the latest value, recomputes, and tries again.
When it works well: Read-heavy workloads where conflicts are infrequent. If only 1 in 1,000 writes actually collides, the retry cost is negligible and you avoid the overhead of locking entirely.
When it breaks down: Under high contention, retries pile up. If 50 transactions are trying to update the same row simultaneously, 49 of them fail and retry, then 48 fail again, and so on. This "retry storm" wastes CPU and can actually make throughput worse than simple locking would.
See it in practice: In Online Chess, two players submitting moves at the same instant need version checks on the board state to prevent one move from overwriting the other. In Google Calendar, overlapping event creation on the same time slot uses optimistic concurrency to detect and reject double-bookings. The Payment Systems pattern covers how financial systems combine OCC with idempotency keys to prevent duplicate charges.