Spot a circular dependency
Two modules that import each other, two components that render each other, two effects that each trigger the other: a cycle in a directed graph is the shape behind some of the most confusing bugs in a frontend codebase.
Implement a Graph class holding a directed graph as an adjacency list. It is constructed with no arguments and starts empty. addEdge(from, to) records a directed edge from from to to, creating either node if it is not known yet; adding the same edge twice changes nothing, and its return value is not used. nodes() returns an array of every known node id in the order each was first seen, and [] when the graph is empty. neighbors(id) returns an array of the ids that id points at, in the order those edges were added, and [] for a node with no outgoing edges or one that is not in the graph. hasCycle() returns true when following edges can lead from some node back to itself, and false otherwise. hasPath(from, to) returns true when following one or more edges from from reaches to, and false otherwise, including when either id is unknown.
Note what hasPath means for a node and itself: hasPath('a', 'a') is true only when there is a real loop back to a, since it requires at least one edge to be followed.
Examples
const g = new Graph();
g.addEdge("a", "b");
g.addEdge("b", "c");
g.hasCycle(); // false
g.hasPath("a", "c"); // true
g.hasPath("c", "a"); // falseconst g = new Graph();
g.addEdge("a", "b");
g.addEdge("b", "c");
g.addEdge("c", "a");
g.hasCycle(); // true
g.hasPath("a", "a"); // true — loops backconst g = new Graph();
g.addEdge("a", "a"); // self-loop
g.hasCycle(); // trueWhat it has to do
hasCycle()isfalsefor an acyclic graph andtruewhen any loop exists, including a self-loop.hasPathfollows one or more edges and returnsfalsefor unknown ids.neighborslists targets in the order their edges were added, with no duplicates.nodes()lists every node created byaddEdge, in first-seen order.- An empty graph has no nodes and no cycle.
Notes
hasCycleandhasPathare O(V + E) traversals. Both lean on marking visited nodes so each edge is followed at most once.- Cycle detection needs two marks, not one: nodes currently on the recursion stack and nodes already fully explored. Hitting a stack-marked node is the loop.
hasPathrequires following at least one edge, which is whyhasPath('a', 'a')is only true when there is an actual loop back toa.
Stuck? The javascript course covers everything this challenge needs.
- 1.records nodes and neighbours in insertion order
- 2.an acyclic graph has no cycle but has paths
- 3.detects a loop of three nodes
- 4.detects a self-loop
- 5.ignores duplicate edges and handles an empty graph
These assertions run in your browser the moment you hit Run. Pass all of them to claim the XP.
Discussion
Sign in to postNo comments yet. Be the first to say something.