Explainer

Graph theory. Strongly Connected Components (SCCs)

Understand strongly connected components and Tarjan’s index, low-link, and active-stack invariants, with a worked graph and implementation.

On this page

Interactive

Tarjan groups mutually reachable vertices into SCCs

Directed graph

012345

Press Next SCC to reveal the first component.

SCCs collapse to a directed acyclic graph

Within a strongly connected component every vertex can reach every other. Collapsing each component produces a DAG: here {0,1,2} → {3,4} → {5}. Tarjan finds all components in one DFS.

Strongly connected components with Tarjan’s algorithm

In a directed graph, vertices are strongly connected when each can reach the other. A strongly connected component (SCC) is a maximal set with this mutual-reachability property. Collapsing each SCC to one vertex produces a directed acyclic graph, which is useful for analyzing dependencies and reachability.

Tarjan’s one-pass idea

Tarjan’s algorithm finds every SCC in O(V + E) time with one depth-first search (DFS). For each vertex v, index[v] is its discovery number. lowLink[v] is the smallest discovery number reachable from v by following DFS-tree edges and, at most once at the end, an edge to a vertex still on the active stack.

A DFS-tree child propagates its lowLink to its parent after the child finishes. An edge to a vertex still on the stack uses that vertex’s index; an edge to a vertex already removed from the stack is ignored. The active stack is not the same as the recursive call stack: it holds vertices whose SCC has not yet been completed.

When lowLink[v] === index[v], no vertex reachable from v in its DFS subtree can connect back to an earlier active vertex. Thus v is the root of one SCC; pop the active stack through v to collect exactly that component. Starting DFS at each still-unvisited vertex also handles disconnected portions of the graph.

JavaScript implementation

The function accepts an adjacency list graph, where graph[u] lists the 0-based destinations of outgoing edges from u, and returns arrays of 0-based vertices. Assume every listed destination is a valid index. Component order and order within each component are unspecified.

function stronglyConnectedComponents(graph) {
  const n = graph.length;
  const index = Array(n).fill(-1);
  const lowLink = Array(n).fill(0);
  const onStack = Array(n).fill(false);
  const stack = [];
  const components = [];
  let nextIndex = 0;

  function visit(v) {
    index[v] = nextIndex;
    lowLink[v] = nextIndex;
    nextIndex += 1;
    stack.push(v);
    onStack[v] = true;

    for (const w of graph[v]) {
      if (index[w] === -1) {
        visit(w);
        lowLink[v] = Math.min(lowLink[v], lowLink[w]);
      } else if (onStack[w]) {
        lowLink[v] = Math.min(lowLink[v], index[w]);
      }
    }

    if (lowLink[v] === index[v]) {
      const component = [];
      let w;
      do {
        w = stack.pop();
        onStack[w] = false;
        component.push(w);
      } while (w !== v);
      components.push(component);
    }
  }

  for (let v = 0; v < n; v += 1) {
    if (index[v] === -1) visit(v);
  }

  return components;
}

For the graph with edges 0→1, 1→2, 2→0, 2→3, 3→4, 4→3, and 4→5, the result consists of {0,1,2}, {3,4}, and {5} (in any order). The first three vertices form a cycle, vertices 3 and 4 form another, and vertex 5 reaches no vertex that can return to it.

The algorithm uses O(V + E) time and O(V) auxiliary space. This implementation is recursive, so call depth can reach O(V); a very deep graph may exceed the JavaScript runtime’s call-stack limit. Use an iterative DFS implementation when that input size is a concern.

Takeaway: discovery indices order the search, low links reveal the earliest active vertex reachable, and popping at a low-link root completes one SCC.