{
  "id": 711208,
  "title": "Detecting Cycles and Loops in a Dependency Graph",
  "url": "https://urgent.news/2026/08/12/detecting-cycles-and-loops-in-a-dependency-graph",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-08-12T23:12:35.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/multigrid/detecting-cycles-and-loops-in-a-dependency-graph-2eca"
  },
  "original_language": "en",
  "account": "Detecting cycles and loops within a dependency graph is essential for building systems to function properly. A cycle in the graph renders a topological sort impossible, resulting in an error message that fails to indicate the faulty edges. The only actionable outcome is identifying the presence of a cycle. Many developers employ a simple algorithm that maintains a single visited set and reports a cycle when it encounters a node already present in the set. However, this approach is flawed, as demonstrated by a specific example.\n\nConsider a dependency graph resembling a diamond shape, composed of nodes a, b, c, and d with the following edges: a -> b, a -> c, b -> d, and c -> d. This structure is acyclic and can be processed without issues by any dependency resolver. The flawed algorithm mistakenly identifies node d as part of a cycle due to visiting it through both b and c, leading to incorrect results. The root of the problem lies in conflating two distinct concepts: reachability and cycle detection. While the first question determines if a node has been encountered previously, the second question assesses whether the node is currently part of the active recursion stack, indicating a cycle.\n\nTo effectively detect cycles, employ a three-color system based on depth-first search principles: WHITE (unvisited), GREY (currently being visited), and BLACK (fully explored). As nodes are visited, they transition from WHITE to GREY to BLACK. An edge connecting two GREY nodes signifies a cycle, whereas an edge linking a BLACK node represents a shortcut into an already processed region. By applying this rule, the cycle can be accurately identified and reported.\n\nTo implement the cycle detector, represent the graph as an adjacency mapping, with keys representing nodes and values containing a list of nodes they depend on. Prioritize the values for consistent output across runs. The graph can be initialized as follows:\n\ngraph = {\n'app': ['auth', 'billing'],\n'auth': ['db', 'config'],\n'billing': ['db', 'invoice'],\n'invoice': ['billing'],\n'db': ['config'],\n'config': [],\n}\n\nIteratively traverse the graph using an explicit stack, where each frame contains a node and an iterator over its remaining neighbors. Upon popping a frame, the node's color changes to BLACK. Initialize the color of each node as WHITE. Then, iterate through the nodes in sorted order. If a node is not WHITE, skip its processing. Otherwise, set its color to GREY, initialize a path with the node, and push the node along with an iterator for its sorted dependencies onto the stack.\n\nWhile the stack is not empty, retrieve the top node and its iterator. If no more neighbors exist, color the node BLACK and pop the frame from the stack. Continue the loop. If the next neighbor is GREY, a cycle has been detected. Return the path from the current node to the encountered GREY node, along with the encountered node itself. If the next neighbor is WHITE, color it GREY, append it to the path, and push it onto the stack for further exploration.\n\nAfter running the cycle detector on the provided graph, the output will indicate the presence of a cycle: 'dependency cycle: billing - invoice - billing'. To verify the algorithm's accuracy and ensure it does not falsely identify cycles in acyclic structures, remove the back edge between invoice and billing (graph['invoice'] = []) and rerun the detector. In this case, the output should be 'acyclic', confirming that the algorithm correctly distinguishes between cyclic and acyclic graphs.\n\nWhen integrating the cycle detector into continuous integration (CI) pipelines, configure it to return a non-zero exit code when a cycle is present. This modification ensures that the build process halts upon detecting a cycle, preventing the propagation of faulty dependencies throughout the system.\n\nWhile the iterative approach avoids the risk of hitting Python's recursion limit, it is crucial to consider the algorithm's complexity. Both the recursive and iterative versions operate with a time complexity of O(V + E), where V represents the number of vertices (nodes) and E represents the number of edges in the graph. For large graphs containing millions of edges, this complexity is negligible, rendering performance concerns irrelevant. However, when faced with numerous cycles, the iterative approach efficiently identifies and reports the first cycle encountered. For more complex scenarios, alternative techniques such as finding strongly connected components or performing a topological sort using Kahn's algorithm can provide comprehensive insights into the graph's structure and identify all cyclic dependencies.",
  "summary": "A cycle in a build graph, an import graph or a task DAG turns a topological sort into an error message that usually does not say which edges are at fault. Getting the path out is not much harder than getting the boolean, and it is the only version anyone can act on. The version that looks right and is not The first attempt almost everyone writes keeps a single visited set and reports a cycle when…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}