Graphs · Advanced Graph Algorithms

Strongly Connected Components (Kosaraju's)

Finding maximal groups of mutually reachable nodes in a directed graph by running DFS twice - once on the original graph to order nodes by finish time, once on the transposed (edge-reversed) graph in that order - so each DFS tree in the second pass is exactly one strongly connected component.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: the condensation
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

In a directed graph, two nodes are strongly connected when each can reach the other. The maximal groups of mutually reachable nodes are the strongly connected components (SCCs). They're the parts of the graph where following edges can bring you back. Kosaraju's algorithm finds them all in O(V + E) with two traversals. Collapsing each SCC into a single node turns any directed graph into a DAG, which makes many hard-looking questions simple.

We'll go from simple to more technical:

  1. The pattern: Kosaraju's algorithm, two DFS passes with a transpose between them.
  2. A second flavor: the condensation DAG, and the minimum number of starting points.
  3. Where SCCs show up (dependency knots, link structure, reachability).
  4. What breaks it: the order of pass 2, skipping the transpose, and counting nodes instead of components.

The approach

Java

List<List<Integer>> stronglyConnectedComponents(int n, List<List<Integer>> adj) {
    boolean[] visited = new boolean[n];
    Deque<Integer> finished = new ArrayDeque<>();
    for (int v = 0; v < n; v++) if (!visited[v]) fillOrder(v, adj, visited, finished);   // pass 1

    List<List<Integer>> transpose = new ArrayList<>();
    for (int v = 0; v < n; v++) transpose.add(new ArrayList<>());
    for (int u = 0; u < n; u++) for (int v : adj.get(u)) transpose.get(v).add(u);      // reverse every edge

    Arrays.fill(visited, false);
    List<List<Integer>> sccs = new ArrayList<>();
    while (!finished.isEmpty()) {
        int v = finished.pop();                          // latest finisher first
        if (!visited[v]) {
            List<Integer> component = new ArrayList<>();
            collect(v, transpose, visited, component);   // pass 2: one fresh DFS = one SCC
            sccs.add(component);
        }
    }
    return sccs;
}

void fillOrder(int v, List<List<Integer>> adj, boolean[] visited, Deque<Integer> finished) {
    visited[v] = true;
    for (int next : adj.get(v)) if (!visited[next]) fillOrder(next, adj, visited, finished);
    finished.push(v);                                    // pushed only after everything reachable is done
}

Python

def strongly_connected_components(n: int, adj: list[list[int]]) -> list[list[int]]:
    visited = [False] * n
    finished = []                                        # a list as a stack: append / pop
    for v in range(n):
        if not visited[v]:
            fill_order(v, adj, visited, finished)        # pass 1

    transpose = [[] for _ in range(n)]
    for u in range(n):
        for v in adj[u]:
            transpose[v].append(u)                       # reverse every edge

    visited = [False] * n
    sccs = []
    while finished:
        v = finished.pop()                               # latest finisher first
        if not visited[v]:
            component = []
            collect(v, transpose, visited, component)    # pass 2: one fresh DFS = one SCC
            sccs.append(component)
    return sccs


def fill_order(v: int, adj: list[list[int]], visited: list[bool], finished: list[int]) -> None:
    visited[v] = True
    for nxt in adj[v]:
        if not visited[nxt]:
            fill_order(nxt, adj, visited, finished)
    finished.append(v)                                   # pushed only after everything reachable is done

The node that finishes last in pass 1 lies in a "source" component - one that nothing outside it can reach. In the transposed graph that component becomes a sink: a DFS from it can't leave. So pass 2, taking nodes in reverse finish order, always starts inside a component whose exits (in the transposed graph) lead only to components already collected. Each fresh DFS therefore picks up exactly one SCC.

A second flavor: the condensation

Collapse each SCC into a single node and keep the edges between different components: the result is always a DAG, because two components that could reach each other would have been one component. Questions about graphs with cycles often become simple DAG questions there. How many starting points are needed so that every node is reachable from at least one? One per component that no edge enters:

Java

boolean[] entered = new boolean[componentCount];
for (int u = 0; u < n; u++)
    for (int v : adj.get(u))
        if (comp[u] != comp[v]) entered[comp[v]] = true;   // an edge from another component
int starts = 0;
for (boolean e : entered) if (!e) starts++;

Python

entered = [False] * component_count
for u in range(n):
    for v in adj[u]:
        if comp[u] != comp[v]:
            entered[comp[v]] = True                      # an edge from another component
starts = entered.count(False)

Counting nodes with in-degree zero gets this wrong as soon as there's a cycle: a 3-cycle has no node with in-degree zero, yet it needs one starting point.

Where you'll see it

SCCs find circular dependencies (the packages that must be built or loaded together), mutually recursive functions in a call graph, and tightly linked clusters of web pages or accounts. Compilers use the condensation to process groups of mutually dependent definitions in a valid order; 2-SAT solvers decide satisfiability by checking whether a variable and its negation share an SCC.

Which tool for which job?

You needUse
All SCCs, simplest codeKosaraju: finish order, then DFS on the transpose
All SCCs in one pass, no transposeTarjan's low-link algorithm
Questions about a graph with cyclesthe condensation DAG
Fewest starting points to reach everythingcomponents with no incoming edge
Satisfiability of 2-variable clauses (2-SAT)SCCs of the implication graph
Very deep graphsiterative DFS for both passes

Common mistakes

MistakeWhat happensDo this instead
Pass 2 in index orderseparate SCCs merge ({0, 1} from the edge 1->0)reverse finish order from pass 1
Pass 2 on the original grapha source's DFS reaches everything downstreamDFS on the transpose
Counting in-degree-zero nodes for starting pointsa 3-cycle needs 1 start but has nonecount components with no incoming edge
Recursive DFS on a long cyclestack overflowiterative passes

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. With edges 0->2, 2->1, 1->0, 0->3 and 3->4, what are the SCCs?

Show answer

{0, 1, 2}, {3} and {4}. 0, 1 and 2 form a cycle, so each reaches the others. 3 is reachable from 0, but nothing leads from 3 back to 0 - so it's alone; the same holds for 4.

2. Why must pass 2 run on the transposed graph?

Show answer

Pass 2 starts in the component that finished last - a source component. On the original graph a DFS from a source reaches every component downstream of it and would merge them. Reversing the edges turns that source into a sink, so the DFS can only stay inside it.

3. A graph is a single 3-cycle. How many starting points are needed to reach every node, and why does counting in-degree-zero nodes fail?

Show answer

One - any node of the cycle reaches the other two. Every node has an incoming edge, so the in-degree count says 0. The right count is over the condensation: one start per SCC that no edge enters from another SCC.

Key takeaways

  • An SCC is a maximal set of nodes that can all reach each other.
  • Kosaraju: DFS to record finish order, then DFS on the transposed graph in reverse finish order - each fresh DFS is one SCC.
  • Both the order and the transpose are required; either mistake merges components.
  • Collapsing SCCs gives a DAG (the condensation), where cyclic-graph questions become DAG questions.
  • O(V + E) time and space; Tarjan's algorithm does it in one pass.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
SCCs, simplestKosaraju
SCCs, one passTarjan
Starting pointssources of the condensation
Make it strongly connectedmax(sources, sinks) new edges
2-SATSCCs of the implication graph

Snippets worth knowing by heart

Java

// Kosaraju pass 2: transpose, in reverse finish order
for (int s : finishOrderReversed) {
    if (comp[s] != -1) continue;
    Deque<Integer> st = new ArrayDeque<>(List.of(s)); comp[s] = c;
    while (!st.isEmpty()) { int u = st.pop(); for (int v : radj.get(u)) if (comp[v] == -1) { comp[v] = c; st.push(v); } }
    c++;
}

Python

# 2-SAT: clause (a or b) -> implications
adj[neg(a)].append(b)
adj[neg(b)].append(a)
unsat = any(comp[x] == comp[neg(x)] for x in literals)

The traps

  • Pass 2 in index order: separate components merge. Process nodes in reverse finish order from pass 1.
  • No transpose: a DFS from a source reaches everything downstream. Run pass 2 on the reversed graph.
  • Counting nodes, not components: a 3-cycle has no in-degree-zero node but needs one start. Count components with no incoming edge.
  • Recursion depth: a long cycle overflows the stack. Make both passes iterative.

Staff-level points, one line each

  • Tarjan and Kosaraju found the same SCCs on 300 random digraphs.
  • Tarjan emits SCCs in reverse topological order of the condensation.
  • 2-SAT via SCCs matched brute force on 300 formulas, and its assignments satisfied them.
  • The fewest edges to make a graph strongly connected is max(sources, sinks); brute force agreed on 60 graphs.
  • Recursive pass 1 overflowed Java's stack on a 100,000-node cycle; the iterative version found 1 SCC.

Say it in an interview

  • "SCCs are groups where every node reaches every other; collapsing them gives a DAG."
  • "Kosaraju: finish order on the graph, then DFS on the transpose in reverse finish order."
  • "Tarjan does it in one pass with low-links."
  • "2-SAT reduces to SCCs: x and NOT x in one component means unsatisfiable."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Strongly Connected Components (Kosaraju's) free

Strongly Connected Components (Kosaraju's Algorithm)

HardProCoding problemJava · Python

Given a directed graph with n nodes (labeled 0 to n-1) and a list of directed edges, find its strongly connected components (SCCs): maximal groups of nodes where every node can reach every other node in the same group.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Minimum Starting Points to Reach Every Node

HardProCoding problemJava · Python

A directed graph has n nodes labeled 0 to n-1. Choose as few starting nodes as possible so that every node is reachable from at least one of them (a starting node reaches itself). Return that minimum number.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Strongly Connected Components (Kosaraju's) free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime