A Bit of History
Kosaraju's algorithm has one of the more unusual origin stories in this series: computer scientist S. Rao Kosaraju devised it around 1978, but never published it himself — it circulated as an unpublished course handout at Johns Hopkins University. Three years later, in 1981, Micha Sharir independently discovered and published an equivalent two-pass technique in a formal paper. The algorithm is now universally credited to both — "Kosaraju's algorithm" for the original unpublished insight, sometimes "Kosaraju-Sharir" in more careful references — a reminder that an idea circulating informally among students and colleagues can shape a field just as much as a published paper.
SCC Foundations
In a directed graph, reachability is not automatically mutual. A strongly connected component (SCC) is a maximal set $C$ such that every $u,v\in C$ satisfy $u\leadsto v$ and $v\leadsto u$. “Maximal” means no additional vertex can be added while preserving that property.
Movement is reversible
Vertices may need several hops, but every directed trip between members has a directed return route.
Movement is one-way
Collapse each SCC to one node. The resulting condensation graph is always a DAG; otherwise a component-level cycle would merge into one larger SCC.
Rooms and One-Way Corridors
Inside each room you can walk between any two spots. Corridors between rooms are one-way and never form a room-level cycle. Kosaraju first learns an order of rooms, then reverses every corridor so it can explore one sealed room at a time.
Transposing a graph reverses every edge: $(u,v)\in E$ becomes $(v,u)\in E^T$. The SCC partition itself does not change, because every path inside an SCC simply reverses into a path in the opposite direction.
Working Principle
Kosaraju's algorithm separates ordering from collection:
- Order: run DFS on the original graph $G$. Append a vertex only after all reachable unvisited neighbors finish.
- Reverse: build the transpose $G^T$ by reversing every edge.
- Collect: process vertices on $G^T$ in decreasing finish time. Every new DFS tree is one SCC.
Append on Exit, Not Entry
Discovery time says when DFS first sees a vertex. Finish time says that everything reachable through its DFS subtree has been dealt with. Kosaraju needs the second fact, so a vertex goes onto the order list during backtracking.
Finishing-Order Intuition
For a component $C$, define $f(C)$ as the largest finish time among its vertices in Pass 1. If the condensation DAG contains an edge $C\to D$, then $f(C)>f(D)$. Either DFS first reaches $D$ from $C$ and finishes $D$ before returning, or $D$ was already completely finished before $C$ was discovered. In both cases, $C$ finishes later.
Therefore, among unprocessed components, the vertex with largest finish time lies in a source SCC of the original condensation DAG. Transposition makes it a sink. This source-to-sink conversion is the engine of the algorithm.
A Valid Alternative Convention
Some presentations run Pass 1 on $G^T$ and Pass 2 on $G$. That is also correct, provided the decreasing-finish order comes from the graph used in the first pass. Mixing the order from one convention with the traversal graph from the other breaks the proof.
Worked Example
Take edges $A\to B$, $B\to C$, $C\to A$, $C\to D$, $D\to E$, and $E\to D$. With vertices and adjacency lists visited in the displayed order, the first DFS begins at A and reaches the entire graph.
The exact order inside an SCC can change with adjacency order. What matters is the component-level ordering: every vertex of the source component $\{A,B,C\}$ is processed before an unvisited vertex can seed $\{D,E\}$.
Why the Transpose Trick Works
Consider the first unassigned vertex $v$ in decreasing finish order, and let $C$ be its SCC. By the finishing-time lemma, $C$ is a source among the remaining components of the original condensation DAG. Therefore $C$ is a sink in the remaining condensation DAG of $G^T$.
A DFS starting at $v$ on $G^T$ can reach every vertex in $C$ because transposition preserves strong connectivity. It cannot reach any other unassigned component because $C$ has no outgoing component edge in $G^T$. So the DFS tree is exactly $C$. Mark it assigned and repeat the same argument on the remaining condensation DAG.
The Two Jobs Are Different
Pass 1 does not identify components; it identifies a safe seed order. Pass 2 does not need finish times of its own; it uses that order to collect one sealed component at a time.
Complexity Analysis
Each DFS examines every vertex once and every outgoing edge once. Building the transpose scans the edge list once:
$$\text{Time: } O(V + E) \qquad \text{Space: } O(V + E)$$
| Operation | Time | Extra state |
|---|---|---|
| Build $G$ and $G^T$ | $O(V+E)$ | Two adjacency lists |
| Pass 1 on $G$ | $O(V+E)$ | visited + finish order |
| Pass 2 on $G^T$ | $O(V+E)$ | visited + component output |
The output itself can contain all $V$ vertices. Recursive DFS may also use $O(V)$ call-stack frames on a long path; an iterative frame stack is safer when graphs can be deep.
Implementation
There are two details worth checking against the code:
- Every vertex must participate in Pass 1. Loop over the complete vertex set so isolated vertices become singleton SCCs.
- Append after recursion.
finish_order.append(u)belongs after every outgoing neighbor has been processed.
Recursion Depth
The recursive listings are concise, but a directed chain can make the call stack $V$ levels deep. In production, raise the language-specific recursion limit only with care, or translate DFS to an explicit stack that stores both a vertex and its next-neighbor index.
Building the Condensation DAG
Once Pass 2 assigns a component ID comp[v] to every vertex, building the component graph is straightforward. For each original edge $u\to v$, add comp[u] → comp[v] when the IDs differ. Deduplicate repeated component pairs if downstream code expects a simple DAG.
| Original edge | Component test | Condensation action |
|---|---|---|
u → v | comp[u] == comp[v] | Ignore; it stays inside one SCC |
u → v | comp[u] != comp[v] | Add comp[u] → comp[v] |
With this article's convention—Pass 1 on $G$, Pass 2 on $G^T$—the SCCs are discovered from sources toward sinks in the original condensation DAG. That component order is therefore already a valid topological order, although vertex order inside each SCC remains arbitrary.
Real-World Applications
2-SAT
Create an implication graph. A formula is unsatisfiable exactly when some literal $x$ and its negation $\neg x$ lie in the same SCC. A satisfying assignment can be derived from the condensation order when no conflict exists.
Dependency analysis
Mutually dependent modules, packages, or build targets form SCCs. Collapsing them exposes the acyclic order between dependency clusters; whether a cycle is an error depends on the system.
State-transition systems
An SCC is a region in which every state can return to every other. Sink SCCs identify recurrent regions from which the process cannot escape.
Reachability compression
Replacing thousands of mutually reachable vertices by component IDs turns an arbitrary directed graph into a DAG, enabling dynamic programming and cleaner reachability reasoning.
Kosaraju or Tarjan?
| Property | Kosaraju | Tarjan |
|---|---|---|
| DFS passes | Two | One |
| Transpose required | Yes | No |
| Core idea | Finish order + reversal | Low-link values + active stack |
| Asymptotic time | $O(V+E)$ | $O(V+E)$ |
| Teaching advantage | Connects directly to the condensation DAG | Shows how DFS can detect component boundaries online |
Pitfalls & Debugging
Six Common Failure Modes
- Appending on discovery: record a vertex only after its DFS descendants finish.
- Using increasing finish time: Pass 2 must use the reverse of the append-on-exit list.
- Forgetting to clear visited: Pass 2 needs a fresh visited set.
- Partially transposing: reverse every directed edge, including parallel edges and self-loops.
- Omitting isolated vertices: the outer DFS loop must use the full vertex set, not only adjacency keys.
- Assuming a canonical order: neighbor iteration may change vertex and SCC order without changing the partition.
Exercises
- Counterexample: run Pass 2 in increasing finish order on the two-SCC example. Show how DFS leaks from $\{D,E\}$ into $\{A,B,C\}$ in $G^T$.
- Trace: add an isolated vertex F. Where does it appear in the finish list, and what SCC does Pass 2 return?
- Proof: show that transposing a graph leaves its SCC partition unchanged.
- Construction: assign component IDs and build the deduplicated condensation DAG for the worked example.
- Implementation challenge: replace both recursive DFS functions with explicit frame stacks while preserving append-on-exit behavior.
Mental Model to Keep
Pass 1 orders the hidden component DAG from upstream to downstream. Reversing edges turns the next upstream component into a dead end. Pass 2 can explore all of that SCC—and nothing beyond it.