</> MAANG.io
coding interview ยท 101

Foundations

Master coding interviews with comprehensive coverage of data structures, algorithms, and problem-solving techniques. Progress from fundamentals to advanced topics with expertly curated content.

0/255 solved 0% complete

Cycle Detection in Graphs

What is this?

A cycle is a loop โ€” a path that wanders away from a starting point and eventually circles right back to it. Sometimes loops are fine, but often they mean disaster: a to-do list where task A waits on task B, B waits on C, and C waits on A can never get started. Cycle detection is how you spot these loops before they cause a deadlock. The one decision that drives everything is whether your connections are two-way streets or one-way streets, because each needs a different detective technique.

flowchart TD A["Is the graph one way or two way"] --> B["Two way use Union Find or parent skip walk"] A --> C["One way walk and track the active path"] C --> D["Step onto a node still on the path means a loop"] B --> E["Two endpoints already joined means a loop"]

๐Ÿ’ก Fun fact: databases hunt for cycles every day. When transactions wait on each other in a ring โ€” A holds a row B wants, B holds a row A wants โ€” the system has a deadlock. The database builds a "wait-for graph," finds the directed cycle, and kills one transaction (the "victim") to break the loop so the rest can proceed.

๐Ÿ”“ The problem in this chapter is free. Sign in with Google or Microsoft to start solving.


The one-line idea: a cycle is a path that loops back onto itself, and finding one is the difference between "this can be ordered/resolved/executed" and "this deadlocks forever." The whole chapter rests on one fork in the road: is the graph undirected (use Union-Find or DFS-with-parent) or directed (use DFS recursion-stack coloring or Kahn's topological sort)? Pick the wrong tool for the wrong graph and you'll get the wrong answer with confident-looking code.


1. What cycle detection really means

A cycle is a path that starts and ends at the same vertex without reusing an edge. Detecting one tells you a graph can't be safely linearized โ€” no valid ordering, possible deadlock, infinite loop. The catch is that "what counts as a cycle" depends entirely on whether edges have direction:

Undirected vs directed cycles: an undirected graph a-b-c-d where a cycle is any path back via a different edge, beside a directed graph a to b to c to a where a cycle is a path back to a node still on the active DFS path

The crucial split:

  • Undirected graphs. Walking aโ€”b then back bโ€”a is not a cycle โ€” it's the same edge. A real cycle means reaching an already-visited node through a different edge. Tools: Union-Find (two endpoints already in the same set โ‡’ this edge closes a loop) or DFS that ignores the parent you came from.
  • Directed graphs. Direction matters: a cycle is a back-edge that points to a node still sitting on your current recursion path. Tools: DFS with white/gray/black coloring (a recursion-stack check) or Kahn's topological sort (nodes left unprocessed โ‡’ cycle).

Using an undirected technique on a directed graph is the classic disaster โ€” see the pitfalls below.


2. Where you'll actually meet this

Cycle detection is a workhorse of systems and infrastructure:

  • Deadlock detection (distributed systems & databases). A wait-for graph of who-holds-what / who-wants-what deadlocks exactly when it has a directed cycle. Databases detect it, then abort a victim transaction.
  • Dependency cycles. Build tools (Make, Bazel, Gradle) and package managers (pip, npm, Cargo) topo-sort dependencies; a cycle is an unsatisfiable, hard error.
  • Garbage collection. Reference-counting GCs leak on reference cycles; cycle collectors exist precisely to find and reclaim them.
  • Spreadsheets & dataflow engines. Circular formula references are directed cycles flagged before evaluation.
  • Networking & finance. Routing-loop prevention; arbitrage = a profitable cycle in a currency graph; fraud rings = suspicious cycles in transaction graphs.

If a problem says circular dependency, deadlock, prerequisites, "is this a valid ordering," reference cycle โ€” you're in this chapter.


3. The cycle-detection toolkit

Choose by graph type first, then by what else you need:

  1. Undirected โ€” Union-Find (disjoint set). Process edges one at a time. If both endpoints already share a root, this edge forms a cycle. Near-O(Eยทฮฑ(V)), no traversal needed, perfect for streaming edges. (detail below.)
  2. Undirected โ€” DFS with parent-skip. DFS the graph; if you reach an already-visited neighbor that isn't the node you came from, that's a cycle. O(V+E).
  3. Directed โ€” DFS 3-color / recursion-stack. Color white (unseen) โ†’ gray (on current path) โ†’ black (done). Stepping onto a gray node = a back-edge = a cycle. O(V+E), early-exits the instant a cycle appears.
  4. Directed โ€” Kahn's topological sort. Repeatedly remove indegree-0 nodes. If you can't remove all of them, the leftovers form a cycle. O(V+E), iterative (no recursion depth risk), and yields a topological order for free.

Rule of thumb: undirected โ‡’ Union-Find when edges arrive one-by-one, DFS-parent when you already have the graph. Directed โ‡’ DFS-color for shortest code and early exit, Kahn when you also want the ordering.


4. A 30-second worked example (catch a back-edge)

Directed graph a โ†’ b โ†’ c โ†’ a. Run DFS from a, coloring as you go:

DFS 3-color trace on a to b to c to a: a, b, c each colored GRAY as they enter the path, then edge c to a finds color[a]==GRAY, a back-edge signalling a cycle

The edge c โ†’ a points at a node still gray (still on the active recursion path), which is exactly the signature of a cycle. Had a been black (fully finished) instead, that same edge would be harmless โ€” a cross/forward edge, not a loop. That gray-vs-black distinction is the entire trick of directed cycle detection.


5. Problems in this chapter

โ–ถ Course Schedule

Decide whether all tasks can be finished given "B before A" prerequisites โ€” i.e. is the dependency graph a DAG? Build a directed graph, then either DFS with white/gray/black coloring (back-edge โ‡’ cycle) or Kahn's indegree BFS (leftover nodes โ‡’ cycle). The answer is "finishable โŸบ acyclic โŸบ a topological order exists."
Pattern: directed cycle detection / topological sort. Target: O(V + E) time, O(V + E) space.


6. Common pitfalls ๐Ÿšซ

  • Using an undirected technique on a directed graph (or vice versa). Union-Find and "ignore the parent" only make sense without direction. On a directed graph you must track the recursion stack (gray nodes) or use Kahn โ€” otherwise you'll miss cycles or hallucinate them.
  • Confusing "visited" with "on the current path." A node already fully explored (black) is fine to revisit; only a node still on the active path (gray) signals a cycle. Collapsing those two states is the most common directed-cycle bug.
  • Wrong edge direction. "A depends on B" must become a consistent edge convention (e.g. B โ†’ A). Reverse it and your topo sort / cycle check answers a different question.
  • Forgetting disconnected components. Start a fresh DFS / seed Kahn from every unvisited node, not just node 0 โ€” a cycle can hide in a component you never entered.
  • Ignoring self-loops. [x, x] is a length-1 cycle; make sure your check flags it.
  • Deep recursion on long chains. Recursive DFS can blow the stack on a pathological chain โ€” prefer Kahn (iterative) when V is large.

7. Key takeaways

  1. Decide undirected vs directed before writing a line. That single choice dictates the entire algorithm; mixing them is the headline mistake.
  2. Undirected: Union-Find (same set โ‡’ cycle) for streamed edges, or DFS that skips the parent for a held graph.
  3. Directed: DFS with white/gray/black โ€” a back-edge into a gray node is a cycle โ€” or Kahn's indegree BFS, where leftover nodes โ‡’ cycle and you also get a topological order.
  4. Directed acyclic โŸบ a topological order exists, which is why cycle detection and topo sort are two views of the same question.
  5. All these run in O(V+E); cover every component, handle self-loops, and watch recursion depth.
  6. It powers real infrastructure โ€” deadlock detection, dependency resolution, GC reference cycles โ€” which is exactly why interviewers reach for it.

Course Schedule

Core idea: "Can you finish all the courses given their prerequisites?" is asking one thing in disguise: is the dependency graph acyclic? Model each course as a node and each "B must come before A" as a directed edge. If you can lay every node out in a single line where every edge points forward โ€” a topological order โ€” you can finish. The only thing that makes that impossible is a cycle: A waits on B, B waits on A, nobody can go first. So this whole problem reduces to detecting a cycle in a directed graph.


1. The problem, in a fresh setting

You run the build system for a large codebase. Each module must be compiled, but some modules #include or import others, so they must be built after their dependencies. You're handed a list of pairs [module, dependency] meaning "build dependency before module." Your job: decide whether the whole project can be built at all, or whether two modules are stuck waiting on each other forever.

Formally: given numTasks tasks labeled 0 โ€ฆ numTasks-1 and a list deps where deps[i] = [a, b] means b must be done before a, return true if there's an order that satisfies every dependency, else false.

numTasks deps Answer Why
3 [[1,0],[2,1]] true order 0 โ†’ 1 โ†’ 2 works
2 [[1,0],[0,1]] false 0 and 1 each wait on the other
4 [] true no constraints โ€” any order works

An edge [a, b] means "a depends on b." We'll draw it as b โ†’ a ("b enables a"), so a valid build is just following the arrows.


Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account โ€” no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.