A deadlock is a circular wait: Transaction A holds a lock on Record 1 and is waiting for a lock on Record 2; Transaction B holds the lock on Record 2 and is waiting for Record 1. Neither can proceed. The platform detects this within seconds and aborts one of the transactions with UNABLE_TO_LOCK_ROW, letting the other finish.
The textbook scenario
Time Tx A Tx B
0 Locks Account 1
1 Locks Account 2
2 Tries to lock Account 2 → waits
3 Tries to lock Account 1 → waits
4 Salesforce detects cycle. Tx A (or B) is aborted with UNABLE_TO_LOCK_ROW.
The aborted transaction rolls back entirely. The surviving one commits normally. Your code has to handle the rollback path — usually by retrying with a delay or kicking the work to a Queueable.
Where deadlocks come from in Apex
In practice, almost no one writes FOR UPDATE patterns that deadlock. Real-world Apex deadlocks come from implicit locks the platform takes on your behalf:
- Master-detail child inserts lock the parent. If two transactions insert children pointing to the same parent in conflicting orders, they can deadlock.
- Lookup with
Reparentable Master Detaildisabled behaves similarly. - Sharing-rule recalculation triggered by ownership changes locks broad swaths of records.
- Triggers on related objects that DML each other in cross-cutting orders.
- Rollup summaries force locks on parents when children change.
The classic Salesforce deadlock pattern:
Tx A: insert Opportunity (locks parent Account 1)
insert OpportunityLineItem (waits for Account 2 lock held by Tx B)
Tx B: insert Opportunity (locks parent Account 2)
insert OpportunityLineItem (waits for Account 1 lock held by Tx A)
If two integrations are running concurrently and each batch happens to touch the same parent accounts in different orders, you’ll see UNABLE_TO_LOCK_ROW errors in the logs.
Why Salesforce kills one rather than waiting
Without deadlock detection, both transactions would wait forever. The platform’s deadlock detector scans the wait-for graph, finds the cycle, and picks a “victim” — usually the cheaper transaction to abort. The victim sees the rollback as a QueryException or DmlException with UNABLE_TO_LOCK_ROW. The other transaction proceeds as if nothing happened.
Detecting deadlocks in production
Deadlocks show up as UNABLE_TO_LOCK_ROW errors in:
- Apex job execution logs (batch, queueable, scheduled)
- Inbound integration error responses
- Trigger error messages in the Setup → Apex Jobs → Failed list
Salesforce does not distinguish in the error message between “the row was locked too long by another transaction” and “you were in an actual deadlock cycle.” Both surface as the same error. The deeper cause has to come from log analysis — typically by correlating the error timestamp with concurrent jobs touching the same parent records.
A minimal reproducer
You almost never write this code intentionally, but it’s the cleanest mental model:
// Anonymous Apex in two browser tabs, run quickly one after the other
Account a1 = [SELECT Id FROM Account WHERE Name = 'A1' LIMIT 1 FOR UPDATE];
// pause...
Account a2 = [SELECT Id FROM Account WHERE Name = 'A2' LIMIT 1 FOR UPDATE];
update a1; update a2;
// In the second tab, the same code with A1/A2 swapped — deadlock.
In real production, the locks are implicit, not from FOR UPDATE, but the cycle is the same.
Comparison: lock contention vs deadlock
| Symptom | Lock contention | Deadlock |
|---|---|---|
| Cause | One transaction is slow; others wait | Two transactions each hold what the other wants |
| Resolution | Times out at 10 seconds, throws UNABLE_TO_LOCK_ROW | Platform aborts one immediately |
| Error message | UNABLE_TO_LOCK_ROW | UNABLE_TO_LOCK_ROW (same!) |
| Mitigation | Reduce hold time, batch smaller | Order DMLs consistently, lock in stable order |
Both produce the same error code, which is why deadlock diagnosis in Salesforce is mostly log forensics.
What interviewers are really looking for
The interviewer wants to see that you understand deadlock as a concurrency phenomenon, not a Salesforce quirk. The good answer mentions: (a) circular wait, (b) platform detection and victim abort, (c) the fact that most Apex deadlocks come from implicit locks on parent records during master-detail or rollup operations, and (d) that the surfaced error is the same UNABLE_TO_LOCK_ROW as ordinary lock contention. Bonus points for naming a real-world mitigation — see How to avoid deadlocks.
Verified against: Apex Developer Guide — Avoiding Deadlocks, Record Locking Cheat Sheet. Last reviewed 2026-05-17 for Spring ‘26 release.