Skip to main content
Articles/Java/tarjans-strongly-connected-components-algorithm
Series: Advanced Algorithms•Part 7 of 7
Scenario Article

Tarjan’s Algorithm: Finding Strongly Connected Components (SCC) step-by-step

Concept: Graph Reachability and Strongly Connected Components

Abstract Algorithms•Jun 18, 2026
💡 Executive Summary

In directed graphs, a Strongly Connected Component (SCC) is a maximal subset of vertices where every vertex is reachable from any other vertex in the subset.

🎯 System Scenario & Requirements

System Goal

In directed graphs, a Strongly Connected Component (SCC) is a maximal subset of vertices where every vertex is reachable from any other vertex in the subset.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • TLDR: In directed graphs, a Strongly Connected Component (SCC) is a maximal subset of vertices where every vertex is reachable from any other vertex in the subset.
  • Finding these components is crucial for resolving circular dependencies.
  • Tarjan's algorithm finds all SCCs in a single Depth-First Search (DFS) pass, achieving an optimal $O(V + E)$ time complexity.

Constraints & Assumptions

  • Recursion Depth Limits: For massive graphs (e.g., millions of nodes in a social network), a standard recursive DFS can exhaust the JVM call stack, causing a StackOverflowError .
  • Mitigation: Implement Tarjan's algorithm using an iterative DFS structure.

⚡ Execution Stages & Decision Trace

1

Stage 1 — Mechanics: DFS Traversal and Stack Constraints

Action / Decision:

To find SCCs, we run a Depth-First Search (DFS) traversal.

Component Behavior:

The key challenge is to identify the "entry point" of each SCC during our traversal.

Observable Output: We call this entry point the root of the strongly connected component.
Read Full Stage in Deep Dive ↓
📖

Deep Dive

Detailed Architectural Analysis

Scenario context

In directed graphs, a Strongly Connected Component (SCC) is a maximal subset of vertices where every vertex is reachable from any other vertex in the subset. Finding these components is crucial for resolving circular dependencies. Tarjan's algorithm finds all SCCs in a single Depth-First Search (DFS) pass, achieving an optimal $O(V + E)$ time complexity. This guide details its mechanics, mathematical models, and pro…

This walkthrough focuses on Concept: Graph Reachability and Strongly Connected Components.

Before making a design choice, clarify:

  • Graph reachability is a fundamental concept in computer science.
  • In a directed graph, we say a vertex $v$ is reachable from a vertex $u$ if there exists a directed path starting at $u$ and ending at $v$.
  • Reachability is not always symmetric; in a directed graph, the existence of a path from $u$ to $v$ does not guarantee a return path from $v$ to $u$.

Concepts in play: graph-traversal-algorithms, stack-and-queue-data-structures, jvm-internals-and-garbage-collection, tree-data-structures

Stage 1 — Mechanics: DFS Traversal and Stack Constraints

  • To find SCCs, we run a Depth-First Search (DFS) traversal.
  • The key challenge is to identify the "entry point" of each SCC during our traversal.
  • We call this entry point the root of the strongly connected component.
  • Tarjan's algorithm achieves linear time execution by tracking two integer values for each node during a single DFS pass: 1.

Observable outcome: Tarjan's algorithm achieves linear time execution by tracking two integer values for each node during a single DFS pass: 1.

Stage 2 — Flow: Graph Partitioning Sequence

  • The flowchart below visualizes how Tarjan's algorithm traverses a directed graph, updates node attributes, and extracts SCCs using the stack: To see this in action, let us trace a query on a directed graph with 4 nodes:…
  • The table below traces the state modifications of each node during execution:
flowchart TD
    Start[Start DFS on unvisited Node u] -->|1. Assign ids and low-link| Assign[ids u = counter, low u = ids u]
    Assign -->|2. Push to Stack| Push[Push u to stack]
    Push -->|3. For each neighbor v| Loop[Check neighbor state]
    Loop -->|v not visited| Recurse[Recurse DFS v]
    Recurse -->|On return| UpdateUnvisited[low u = min low u, low v]
    Loop -->|v on stack| UpdateOnStack[low u = min low u, ids v]
    Loop -->|Loop finished| RootCheck{low u == ids u?}
    RootCheck -->|Yes| PopStack[Pop stack until u retrieved]
    PopStack -->|Collect SCC| SaveSCC[Save popped nodes as SCC]
    RootCheck -->|No| Return[Return to parent DFS]

Observable outcome: The table below traces the state modifications of each node during execution:

Stage 3 — Deep Dive: Tree Back-Edges and Cycle Identification

  • Implementing Tarjan's algorithm efficiently requires understanding how back-edges detect cycles.
  • Low-Link Value Tracking Internals The low-link value of a node is updated dynamically based on whether a neighbor is currently on the stack.
  • If a neighbor $v$ has not been visited , we recursively traverse it.
  • When the traversal returns, we update the parent's low-link value: $$ \text{low}[u] = \min(\text{low}[u], \text{low}[v]) $$ This propagates low-link values back up the DFS tree.

Observable outcome: When the traversal returns, we update the parent's low-link value: $$ \text{low}[u] = \min(\text{low}[u], \text{low}[v]) $$ This propagates low-link values back up the DFS tree.

Stage 4 — Advanced Concepts: Cross-Edges and Stack Containment Rules

  • During DFS, we categorize edges into four types: tree edges, forward edges, back-edges, and cross-edges.
  • Back-Edges: Point from a node to one of its ancestors in the DFS tree.
  • These edges always represent cycles, so we use them to update low-link values.
  • Cross-Edges: Point from a node to a previously visited node that is not its ancestor.

Observable outcome: Cross-Edges: Point from a node to a previously visited node that is not its ancestor.

Stage 5 — Applications: From Circular Dependencies to Social Graphs

  • Dependency Analysis in Package Managers: Checking for dependency cycles in package structures (e.g.
  • npm dependencies) to prevent compilation loops.
  • Garbage Collection Reference Tracing: Modern runtimes use graph reachability to find cyclic object references and clean up unreferenced memory groups.
  • Optimizing Database Queries: Breaking recursive SQL queries down into independent DAG components to parallelize database executions.

Observable outcome: Optimizing Database Queries: Breaking recursive SQL queries down into independent DAG components to parallelize database executions.

Stage 6 — Practical Implementation: Java Reference Code

  • Here is a complete, production-ready Java implementation of Tarjan's Strongly Connected Components algorithm.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

public class TarjansSCC {
    private int idCounter = 0;
    private int[] ids;
    private int[] low;
    private boolean[] onStack;
    private Stack<Integer> stack;
    private List<List<Integer>> sccs;

    public List<List<Integer>> findSCCs(int numVertices, List<List<Integer>> adjacencyList) {
        if (numVertices <= 0 || adjacencyList == null) {
            return new ArrayList<>();
        }

        ids = new int[numVertices];
        low = new int[numVertices];
        onStack = new boolean[numVertices];
        stack = new Stack<>();
        sccs = new ArrayList<>();

        // Initialize discovery IDs with a sentinel value (-1)
        Arrays.fill(ids, -1);

        for (int i = 0; i < numVertices; i++) {
            if (ids[i] == -1) {
                dfs(i, adjacencyList);
            }
        }

        return sccs;
    }

    private void dfs(int u, List<List<Integer>> adj) {
        ids[u] = idCounter;
        low[u] = idCounter;
        idCounter++;

        stack.push(u);
        onStack[u] = true;

        for (int v : adj.get(u)) {
            if (ids[v] == -1) {
                // Neighbor not visited yet: recurse
                dfs(v, adj);
                low[u] = Math.min(low[u], low[v]);
            } else if (onStack[v]) {
                // Back-edge found: update low-link value using neighbor's index
                low[u] = Math.min(low[u], ids[v]);
            }
            // If ids[v] != -1 and !onStack[v], this is a cross-edge to a completed SCC. Ignore it.
        }

        // If u is the root of a strongly connected component, collect the nodes
        if (low[u] == ids[u]) {
            List<Integer> component = new ArrayList<>();
            while (true) {
                int node = stack.pop();
                onStack[node] = false;
                component.add(node);
                if (node == u) {
                    break;
                }
            }
            sccs.add(component);
        }
    }
}

Observable outcome: Here is a complete, production-ready Java implementation of Tarjan's Strongly Connected Components algorithm.

End-to-end outcome

After working through the stages, you should be able to explain:

  • Forgetting to check the onStack constraint: If you update the low-link value of a node using a cross-edge to a completed SCC (where onStack[v] is false), you will corrupt the low-link values, leading to incorrect SCC pa…
  • Always check onStack[v] before updating low[u] .
  • Definition: An SCC is a maximal subset of vertices where every vertex can reach every other vertex.
  • Complexity: Time complexity is $O(V + E)$; space complexity is $O(V)$.

Alternatives considered

Decision Guide: Tarjan vs. Kosaraju vs. Gabow

Use this table to choose the right SCC algorithm for your graph application:

Failure and degraded modes

Trade-offs and Failure Modes

  • Detection: Recursion Depth Limits: For massive graphs (e.g., millions of nodes in a social network), a standard recursive DFS can exhaust the JVM call stack, causing a StackOverflowError .
  • User or operator impact: Recursion Depth Limits: For massive graphs (e.g., millions of nodes in a social network), a standard recursive DFS can exhaust the JVM call stack, causing a StackOverflowError .
Series Navigation • Part 7 of 7

Advanced Algorithms

View Full Series Overview →

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit