When two users try to change the same record at the same time, something has to decide who wins and how conflicts are handled. Optimistic and pessimistic locking are the two classic answers. Pessimistic locking assumes conflicts are likely and blocks others up front; optimistic locking assumes conflicts are rare and only checks for them at the moment of writing. Picking the wrong one for your workload leads to either needless slowdowns or surprise lost updates.
What changed in 2026
- Distributed systems pushed optimistic patterns. As apps spread across regions, holding long database locks became impractical, so version-based optimistic concurrency and conflict resolution grew more common.
- ORMs made both easy. Mainstream frameworks now ship first-class support for version columns and row locks, so choosing a strategy is a config decision more than hand-coded work.
- Serverless amplified lock cost. Short-lived functions holding pessimistic locks can starve each other, nudging teams toward optimistic approaches with retries.
How pessimistic locking works
Before reading a record it intends to change, a transaction acquires a lock (for example, SELECT ... FOR UPDATE). Anyone else who wants that row waits until the lock is released. This guarantees no one else can modify the data in the meantime, so lost updates are impossible — but other transactions block, and if locks are held too long you get contention, timeouts, or deadlocks.
How optimistic locking works
No lock is taken while reading. Instead, each row carries a version number or timestamp. When you go to save, the update says, in effect, "set these values where version is still what I read." If someone else changed the row first, the version no longer matches, zero rows update, and your write is rejected. You then retry with fresh data. No one waits — but conflicts cost a redo.
Side by side
| Factor |
Optimistic |
Pessimistic |
| Assumption |
Conflicts are rare |
Conflicts are likely |
| Locks held |
None until commit |
From read to commit |
| Cost of conflict |
Retry the write |
Others wait |
| Risk |
Wasted work on retries |
Contention, deadlocks |
| Best for |
High read, low contention |
Hot rows, high contention |
When to use which
Use optimistic locking when contention is low and reads dominate — most web apps, where two people rarely edit the exact same record at the exact same moment. The occasional retry is cheaper than locking everyone out. Because concurrent writes are really a form of race condition, the same mental model applies here. Use pessimistic locking for genuinely hot rows where collisions are frequent and a lost-update retry is expensive or unsafe: inventory counts, seat booking, and financial balances.
Common pitfalls
- Optimistic without retry logic. If you reject a conflicting write but do not retry, you just moved the failure to the user. Handle the retry.
- Pessimistic locks held across user think-time. Never hold a database lock while waiting for a human to fill out a form. That is a recipe for contention.
- Ignoring deadlocks. Pessimistic locking can deadlock when transactions grab rows in different orders. Lock in a consistent order.
- No version column. Optimistic concurrency needs a version or timestamp field; comparing every column is fragile.
FAQ
Which is faster, optimistic or pessimistic locking?
Under low contention, optimistic wins because nothing blocks. Under high contention, optimistic wastes effort on retries and pessimistic can be more efficient.
Does optimistic locking prevent lost updates?
Yes — the version check rejects a write based on stale data. You still need retry logic to recover gracefully.
Is SELECT ... FOR UPDATE optimistic or pessimistic?
Pessimistic. It acquires a row lock immediately so other transactions must wait.
Can I mix both in one application?
Absolutely. Use optimistic locking for most records and pessimistic locking on the few genuinely hot rows. The right tool depends on the row, not the whole app.
Where to go next