← Back to Computer Fundamentals

Deadlocks

Deadlocks form when concurrent actors wait on one another's locked resources.

Computer FundamentalsConcurrencyDatabases

A deadlock is a failure mode in concurrent systems where two or more actors are permanently blocked because each actor is waiting for another to release something it needs. The actors might be operating system threads, application processes, or database transactions. The common feature is circular waiting with no mechanism to break the cycle.

A simple example is two threads and two locks. Thread A acquires lock 1 and then waits for lock 2. Thread B acquires lock 2 and then waits for lock 1. Neither thread can proceed, and neither thread will release its current lock because both are stuck in the critical section. Databases see the same pattern when two transactions update rows in opposite order and each holds a lock the other one needs.

Classically, deadlock requires four conditions to hold at the same time. There must be mutual exclusion, meaning a resource cannot be shared freely. There must be hold and wait, meaning an actor keeps one resource while waiting for another. There must be no preemption, meaning resources are not forcibly taken away. Finally, there must be circular wait. Break any one of those conditions and deadlock becomes impossible.

That is why prevention strategies usually look mechanical rather than clever. The safest pattern is a global lock ordering rule so every thread acquires shared resources in the same order. Smaller critical sections also help because they reduce the window in which a cycle can form. Timeouts and try_lock style operations can avoid waiting forever, though they move the problem into retry logic and can create livelock if retries are poorly designed.

Detection and recovery are another option. Many relational databases build a wait graph internally and run deadlock detection. When they find a cycle, they abort one transaction so the others can continue. That approach works because a transaction can be rolled back cleanly. In application code, recovery is often harder. Killing a thread is rarely safe, so teams usually prefer prevention plus observability, such as thread dumps, lock metrics, and alerts on requests that exceed normal wait times.

It is useful to separate deadlock from related problems. Starvation means a task keeps getting delayed and never gets a turn, even though the system as a whole is still moving. Livelock means tasks keep reacting to one another but make no real progress. All three are concurrency failures, but the fix is different in each case.

The practical takeaway is that deadlock is not a mysterious edge case. It is a predictable consequence of shared mutable state plus inconsistent resource acquisition. If a system uses locks, semaphores, transactional row updates, or other exclusive resources, deadlock should be treated as a design concern from the start. Consistent ordering, bounded waiting, and recovery paths are usually cheaper than trying to debug a production system that appears idle while every blocked actor waits for something that will never arrive.