---
title: "Deadlock"
slug: "deadlock"
category: "glossary"
tags: ["glossary", "backend", "database", "postgres", "concurrency", "transactions"]
status: "stable"
last_updated: 2026-05-14
summary: "A deadlock occurs when two or more transactions each hold a lock the other needs, creating a cycle that neither can break without external intervention."
related:
  [
    "[[backend/postgres]]",
    "[[glossary/transaction-isolation]]",
    "[[glossary/optimistic-locking]]",
    "[[glossary/acid]]",
    "[[glossary/foreign-key]]",
    "[[glossary/secondary-index]]",
  ]
---

## Overview

This page is the atomic definition. Transaction patterns and lock strategies live at [[backend/postgres]].

## Definition

A deadlock is a circular lock-wait: transaction A holds lock L1 and waits for lock L2 held by transaction B, while transaction B holds L2 and waits for L1. Neither can proceed. PostgreSQL detects deadlocks automatically (every `deadlock_timeout` milliseconds, default 1 s) and terminates one of the waiting transactions with error code `40P01`. The application must catch the error and retry. Prevention strategies: always acquire locks in a consistent order across transactions (lock rows in primary-key order, not random order); use `SELECT ... FOR UPDATE SKIP LOCKED` for queue-like patterns; keep transactions short to minimize the window for lock conflicts. [[glossary/optimistic-locking]] avoids taking locks entirely for read-modify-write operations by checking a version counter at commit time. High [[glossary/transaction-isolation]] levels (Serializable) do not increase deadlock frequency on their own, but they do increase serialization failure rates, which require similar retry logic.

## When it applies

Expect deadlocks in any multi-table update that touches rows in varying order, in queue processing, and in any feature where two users can concurrently update overlapping data sets. Log deadlocks in production and add retry logic at the application layer.

## Example

```
Transaction A:                          Transaction B:
UPDATE accounts SET bal=bal-50 WHERE id=1;    UPDATE accounts SET bal=bal-50 WHERE id=2;
UPDATE accounts SET bal=bal+50 WHERE id=2; <- UPDATE accounts SET bal=bal+50 WHERE id=1;
-- Both block. Postgres kills one.
```

Fix: always update rows in ascending `id` order.

## Related concepts

- [[backend/postgres]] - `deadlock_timeout`, error code 40P01, and lock monitoring.
- [[glossary/transaction-isolation]] - higher isolation does not prevent deadlocks.
- [[glossary/optimistic-locking]] - an alternative that avoids locks entirely for low-conflict paths.
- [[glossary/foreign-key]] - FK constraint checks acquire locks that contribute to cycles.

## Citing this term

> See [[glossary/deadlock|Deadlock]] (llmbestpractices.com/glossary/deadlock).

## Related

- [[backend/postgres]]
- [[glossary/transaction-isolation]]
- [[glossary/optimistic-locking]]
- [[glossary/acid]]
- [[glossary/foreign-key]]
