StudyHub

Algorithms · Graphs

Cycle detection

Follow the arrows 0 → 1 → 2, and from 2 there's an arrow back to 0, a vertex you are still in the middle of exploring. That arrow closes a loop: a cycle. DFS finds it by colouring vertices: white (not seen), grey (on the current path), black (finished). An edge to a grey vertex means a cycle.

Why it matters: a cycle in a dependency graph means something can never be done (a circular import, a deadlock, an impossible course plan), and DFS spots it in $O(V + E)$.

Prerequisites

  • DFS and recursion.
  • Directed graphs.
  • Union-Find, for the undirected method.

The analogy

Everyday analogy

Imagine walking through a building and unrolling a thread behind you. If you ever reach a room where your current thread already lies, you've walked in a circle.

The grey colour is that thread: it marks the vertices on the path you're exploring right now. Black rooms are finished; meeting one again is fine, it just means two paths join.

How it works

Precondition: the colour method below is for directed graphs. For undirected graphs, use Union-Find, or DFS that ignores the edge back to the parent.

Start with one DFS by hand

Directed edges: 0 → 1, 1 → 2, 2 → 0, 2 → 3. All vertices start white. Start DFS at 0, colour a vertex grey when you enter it and black when you leave. Predict, then open "Check".

Example: predict before you read

Step 1. Enter 0, then 1, then 2. Predict their colours.

Check

All three are grey: they are on the current path 0 → 1 → 2.

Step 2. Cover the next line and guess: at 2, you see the edge 2 → 0. What does it mean?

Check

0 is grey, so the edge points back into the current path: a cycle 0 → 1 → 2 → 0.

Step 3. Remove the edge 2 → 0 and run again. Predict whether any cycle is found.

Check

No. 0, 1, 2, 3 are explored and turn black one by one; no edge ever points to a grey vertex.

Directed graph 0 to 1 to 2 back to 0, and 2 to 3; the back edge 2 to 0 is red
What you just found: grey vertices are on the current DFS path; the red back edge to a grey vertex closes the cycle.

Quick recall: Before reading further, cover the text above and say out loud: what do grey and black mean?

Check your answer

Grey: entered but not finished, so on the current path. Black: finished, along with everything reachable from it.

Finish the picture: the undirected case

Undirected edges A–B, B–C, C–A. Use Union-Find: for each edge, check whether its ends are already in the same group.

  • A–B, then B–C. Predict the groups.
    Check

    A–B: different groups, merge {A, B}. B–C: different, merge {A, B, C}.

  • C–A. Predict the result.
    Check

    C and A are already in the same group, so this edge closes a cycle.

Quick recall: Without scrolling up, say out loud: why is an edge to a black vertex not a cycle?

Check your answer

Everything reachable from a black vertex is already explored and doesn't lead back to the current path, so the edge just joins an old branch.

The rule in plain words

Directed: run DFS from every white vertex. Colour grey on entry and black on exit; an edge to a grey vertex is a back edge, which means a cycle.

Undirected: process edges with Union-Find; an edge whose ends are already connected closes a cycle.

Quick recall: Before you read the code, write the colour rule in your own words. Then check below.

Check your answer

Colour a vertex grey when you start exploring it. For each neighbour: grey means a cycle; white means explore it. When all neighbours are done, colour the vertex black.

The code

Read this code after you have done the DFS by hand. It will make more sense.

int has_cycle_from(int adj[][4], int n, int u, int color[]) {   /* 0 white, 1 grey, 2 black */
    color[u] = 1;                                     /* on the current path */
    for (int v = 0; v < n; v++) {
        if (!adj[u][v]) continue;
        if (color[v] == 1) return 1;                  /* back edge: cycle */
        if (color[v] == 0 && has_cycle_from(adj, n, v, color)) return 1;
    }
    color[u] = 2;                                     /* fully explored */
    return 0;
}
int has_cycle(int adj[][4], int n, int color[]) {     /* color[] starts all 0 */
    for (int u = 0; u < n; u++)
        if (color[u] == 0 && has_cycle_from(adj, n, u, color)) return 1;
    return 0;
}

Definition

What you just did, in one line: a directed graph has a cycle exactly when DFS finds a back edge, an edge to a vertex that is still on the current DFS path (grey).

flowchart TD
  A["colour u grey"] --> B{"next neighbour v?"}
  B -->|"v is grey"| C["cycle found"]
  B -->|"v is white"| D["explore v"]
  B -->|"v is black"| B
  D --> B
  B -->|"no more neighbours"| E["colour u black, return"]
          
Before you scroll past this diagram, cover the right side and guess: at vertex 2, which arrow does the neighbour 0 take? (Answer: "v is grey", so a cycle is found.)

How fast is it?

Quick recall: Before looking at the formula, predict: does checking colours add to DFS's cost?

Check your answer

No, it's one array lookup per edge, so the total stays $O(V + E)$.

It is a DFS with a constant-time colour check per edge:

$$O(V + E) \text{ (lists)}, \qquad O\big(E \, \alpha(V)\big) \text{ (Union-Find, undirected)}$$
MethodGraph typeCost
DFS with three coloursDirected$O(V + E)$
DFS ignoring the parent edgeUndirected$O(V + E)$
Union-Find over the edgesUndirected$O(E \, \alpha(V))$
Kahn's topological sortDirected$O(V + E)$: fewer than $V$ outputs

Warning

Two colours (visited / not visited) are not enough for directed graphs. In 0 → 1, 0 → 2, 1 → 2, vertex 2 is visited twice, but there is no cycle. Only an edge to a grey vertex proves a cycle.

Exam tip

State which graph type you are handling. Directed: grey/black DFS or Kahn. Undirected: Union-Find, or DFS where an edge to a visited vertex other than the parent means a cycle.

Common mistakes

  • Using a single "visited" flag on a directed graph and reporting false cycles.
  • In undirected DFS, treating the edge back to the parent as a cycle.
  • Running DFS only from vertex 0 and missing cycles in other parts of the graph.

Try it yourself

Exercise 1 (warm-up)

Directed edges 0 → 1, 0 → 2, 1 → 2. Is there a cycle? What colour is 2 when 0 → 2 is examined?

Hint

Follow DFS from 0, visiting 1 first.

Answer

No cycle. DFS goes 0 → 1 → 2, and 2 finishes (black). Back at 0, the edge 0 → 2 points to a black vertex, which is not a cycle.

Exercise 2 (a bit harder)

Directed edges 0 → 1, 1 → 2, 2 → 3, 3 → 1. Run the colour DFS from 0: which edge reveals the cycle, and which vertices are on it? Write your answer down before you open it.

Show answer

The path 0 → 1 → 2 → 3 is all grey; at 3, the edge 3 → 1 points to grey vertex 1, a back edge. The cycle is 1 → 2 → 3 → 1; vertex 0 leads into it but isn't part of it.

Did you know?

Did you know?

Operating systems detect deadlocks this way: build a "wait-for" graph of which process waits for which, and a cycle means they are all stuck.

Linked-list cycle detection uses a different trick, Floyd's "tortoise and hare": two pointers at different speeds meet if and only if there is a loop.

Key points to remember

If you remember one thing from each line, remember the because.

  • An edge to a grey vertex means a cycle because grey vertices form the current path, so the edge closes a loop.
  • Edges to black vertices are safe because a finished vertex cannot reach the current path.
  • It costs $O(V + E)$ because it is a single DFS with constant work per edge.
  • Undirected graphs can use Union-Find because an edge inside one component closes a cycle.
  • DFS restarts from every white vertex because a cycle may be in a part not reachable from the first start.

Self-check questions

Answer these in order, out loud. They follow the page, so your answers become the script of your video.

  1. How is DFS cycle detection like unrolling a thread through a building?
    Sample answer

    The thread marks your current path; if you reach a room where the thread already lies, you've walked in a circle.

  2. Walk through the colour DFS on 0 → 1, 1 → 2, 2 → 0, 2 → 3.
    Sample answer

    0, 1, 2 become grey along the path; at 2 the edge 2 → 0 points to grey 0, so there is a cycle 0 → 1 → 2 → 0.

  3. Why does the code colour u black only after the loop?

    A good answer says what grey must represent.

    Sample answer

    Grey must mean "still on the current path". u stays on the path until all its neighbours are explored; marking it black earlier would hide back edges to it.

  4. So when does a directed graph have a cycle, in one sentence?
    Sample answer

    Exactly when a DFS finds an edge to a vertex that is still on the current DFS path.

  5. Why aren't two colours enough for directed graphs?

    A good answer gives the 0 → 1, 0 → 2, 1 → 2 example.

    Sample answer

    Vertex 2 is reached twice there without any cycle. A plain "visited" flag can't tell a back edge (cycle) from an edge into a finished branch.

  6. What does cycle detection cost?
    Sample answer

    $O(V + E)$ with DFS; $O(E \, \alpha(V))$ with Union-Find on undirected graphs.

  7. How would you detect a cycle in an undirected graph?
    Sample answer

    Union-Find over the edges (an edge inside one group is a cycle), or DFS where reaching a visited vertex other than your parent means a cycle.

  8. If you had to teach this to a friend in 2 minutes, what would you leave out, and why?
    Sample answer

    I'd leave out the undirected variants and the deadlock story. I'd keep the thread analogy, the 0 → 1 → 2 → 0 example, and "edge to a grey vertex means a cycle, $O(V + E)$".

External resources

Videos

Review schedule

Come back after 3, 7, 14, then 30 days. Spacing beats cramming.

Feynman prompt

Explain this topic in your own words, as if teaching a beginner.

Explain it in your own words first, then record a 2–3 minute video on your phone.

25:00

Ready to record? Open your phone camera now and record a 2–3 minute explanation.

My notes

Saved only in this browser (localStorage). Nothing is uploaded.