Hook: Interviewers love deadlocks because they test whether you understand that SQL is not just about storing data — it is about coordinating many transactions at once.
Question: What is a deadlock in SQL?
Answer: A deadlock happens when two or more transactions each hold a lock the other one needs, so nobody can move forward. Think of it like two cars meeting head-on in a one-lane bridge: both are waiting, and neither can pass. Databases detect this cycle and abort one transaction so the others can continue.
Interview-Ready Answer: In SQL, a deadlock is a circular wait between transactions. For example, Transaction A locks row 1 and wants row 2, while Transaction B locks row 2 and wants row 1. The database detects that cycle and kills one transaction as the victim, so the other one can finish. In practice, I prevent deadlocks by locking rows in a consistent order, keeping transactions short, and retrying the failed transaction safely.
A lock is the database saying, “this row or table is busy right now.” A deadlock is not just waiting; it is a cycle of waiting. Transaction A is waiting for something held by B, and B is waiting for something held by A. Because each one is holding the thing the other needs, the system cannot resolve it by waiting longer.
The victim is usually chosen by cost: engines try to abort the transaction that is cheapest to roll back, or the one that has done the least work. The exact rule depends on the database engine, but the idea is always the same: sacrifice one transaction to let the rest complete.
| Problem | What happens | How it ends |
|---|---|---|
| Deadlock | Circular wait | DB aborts one tx |
| Lock timeout | Wait too long | Timeout error |
| Serialization failure | Isolation conflict | Tx must retry |
A deadlock is different from a timeout. In a timeout, a transaction may simply have waited too long. In a deadlock, the database knows waiting will never help, because the wait is circular.
Performance note: deadlock detection itself is usually fast; the real cost is wasted work from the aborted transaction and the extra retry. In OLTP systems, deadlocks often show up only under concurrency spikes, not during single-user testing.
UPDATE can deadlock if it touches multiple rows or tables indirectly through triggers or foreign keys.Memory note: if you can explain the cycle, the wait-for graph, and the victim rollback, you understand the core of deadlocks.
Imagine a checkout service in an e-commerce app. One transaction reserves inventory, another writes the order row, and a third updates the customer’s loyalty points. Under normal traffic everything looks fine, but during a flash sale, two requests can touch the same customer and inventory rows in different orders.
What goes wrong: request A locks the inventory row first and then waits on the customer row, while request B locks the customer row first and then waits on the inventory row. The database detects the deadlock, aborts one request, and the app sees an error such as “deadlock detected” or a vendor-specific lock error. Users may see a failed checkout, a retry spinner, or a duplicate attempt if the application retries without idempotency. In logs you often see a lock-wait message, a victim rollback, and a spike in latency at the same minute.
The lesson: the bug is rarely “the database is broken.” It is usually a code-path mismatch: two parts of the app touched the same rows in different orders. The fix is to make every path follow the same order and to retry the failed transaction safely.
-- This example is safe to run as-is.
-- It sets up a tiny table and shows the correct pattern:
-- always touch shared rows in the same order.
--
-- The actual deadlock scenario requires TWO concurrent sessions,
-- so the unsafe version is shown in comments below.
DROP TABLE IF EXISTS inventory;
CREATE TABLE inventory (
product_id INTEGER PRIMARY KEY,
sku VARCHAR(20) NOT NULL UNIQUE,
quantity INTEGER NOT NULL CHECK (quantity >= 0)
);
INSERT INTO inventory (product_id, sku, quantity) VALUES
(1, 'A-100', 10),
(2, 'B-200', 10);
-- Safe transaction: row order is consistent (1 then 2).
BEGIN TRANSACTION;
UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 1 AND quantity > 0;
UPDATE inventory
SET quantity = quantity + 1
WHERE product_id = 2;
COMMIT;
SELECT product_id, sku, quantity
FROM inventory
ORDER BY product_id;
-- Edge case / failure path:
-- If stock is already zero, the guarded UPDATE affects 0 rows.
-- That is not a deadlock; it is a business-rule failure you must handle.
BEGIN TRANSACTION;
UPDATE inventory
SET quantity = quantity - 999
WHERE product_id = 1 AND quantity >= 999;
ROLLBACK;
-- Deadlock-prone pattern (requires two separate sessions; do not run here):
-- Session A:
-- BEGIN TRANSACTION;
-- UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 1;
-- UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 2;
-- COMMIT;
--
-- Session B:
-- BEGIN TRANSACTION;
-- UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 2;
-- UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 1;
-- COMMIT;
--
-- One session can be chosen as the deadlock victim and must ROLLBACK.
-- The application should retry the whole transaction, not just the last UPDATE.Follow-up & Tricky Questions:
Common Mistakes:
Memory Hook: “Two people, one hallway, opposite doors.” Each person holds a door and waits for the other door to open. Nobody can move until one person steps back — that is the aborted victim.
Cheat Sheet:
Practice Tasks: