Coding problem · Medium · Graphs

Find Deadlocked Transactions

Medium Practical tasks

Your on-call dashboard samples the database's lock table (like Postgres pg_locks + pg_blocking_pids) and builds wait-for edges: [waiter, holder] means transaction waiter is blocked by a lock held by holder.

Implement findDeadlocked(waits) that returns every transaction that is part of at least one cycle — those can never make progress without the database aborting one of them. Return the names sorted ascending; return an empty list when there is no deadlock. Transactions that merely wait on a cycle (but are not in it) are not deadlocked.

Example 1

Input: waits = [["T1","T2"],["T2","T1"],["T3","T1"]]
Output: ["T1","T2"]
Explanation: T1 and T2 wait for each other. T3 is blocked, but not part of the cycle.

Example 2

Input: waits = [["A","B"],["B","C"]]
Output: []
Explanation: A chain of waits with no cycle resolves when C commits.

Constraints

  • 0 ≤ waits.length ≤ 1,000
  • Names are non-empty strings
  • A transaction may wait on several others

Hints

Hint 1

Build an adjacency list from waiter to holders.

Hint 2

A node is deadlocked if you can walk from it back to itself.

Hint 3

For large graphs, strongly connected components (Tarjan/Kosaraju) find all cycles in O(V + E).

Write and run your solution

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time O(V · (V + E)) · Space O(V + E)

Treat the edges as a directed graph. A transaction is deadlocked exactly when it lies on a cycle, i.e. it can reach itself. The reference solutions run a DFS from each waiter (O(V·(V+E)), fine for a lock table snapshot); a production monitor would use strongly connected components to find all cycles in one pass.

This is the same check databases perform: Postgres runs it when a lock wait exceeds deadlock_timeout, InnoDB on every wait. See How Deadlocks Happen.

Reference solution

function findDeadlocked(waits) {
  const graph = new Map();
  for (const [waiter, holder] of waits) {
    if (!graph.has(waiter)) graph.set(waiter, []);
    graph.get(waiter).push(holder);
  }
  const reachesItself = (start) => {
    const seen = new Set();
    const stack = [...graph.get(start)];
    while (stack.length) {
      const node = stack.pop();
      if (node === start) return true;
      if (seen.has(node)) continue;
      seen.add(node);
      stack.push(...(graph.get(node) ?? []));
    }
    return false;
  };
  return [...graph.keys()].filter(reachesItself).sort();
}

Common mistakes

  • Reporting every blocked transaction instead of only those on a cycle.
  • Infinite loops when DFS doesn't track visited nodes.
  • Forgetting to sort the result.

Follow-up questions

  • Pick a victim to abort (e.g. the youngest transaction).
  • Find all cycles in O(V + E) with Tarjan's algorithm.