Kosaraju's Algorithm
Find strongly connected components with two DFS passes: record finish order, then DFS the reversed graph in decreasing finish time.
Overview
Kosaraju's algorithm computes Strongly Connected Components with two plain depth-first searches. Pass 1 runs Depth-First Search (DFS) on the original graph and records vertices in finish order (post-order). Pass 2 builds the reverse graph G^T (every edge u → v becomes v → u) and runs DFS from vertices in decreasing finish time. Each DFS tree of pass 2 is exactly one SCC.
It is slightly slower than Tarjan's SCC Algorithm in practice (two traversals, plus building the reversed adjacency list) but the code is two ordinary DFS routines and the proof is short, which makes it a favourite for interviews and for iterative implementations.
Components are produced in topological order of the condensation: the first component found is a source component (nothing outside it points into it).
Intuition
A mental model before the formal terms.
Reversing all edges does not change which vertices are mutually reachable — a round trip u → … → v → … → u reversed is still a round trip. But reversal flips the direction of travel *between* components. If, in the original graph, you can go from component A to component B, then in the reversed graph you can go from B to A only.
Pass 1 finds a vertex that finishes last; it lives in a component with no incoming edges from unfinished components — a "source" of the condensation. In the reversed graph a source becomes a sink: DFS from it cannot leak into any other component, so it paints exactly one SCC. Remove it (mark visited) and the next-latest finisher is again a source among what remains. Repeat.
Tiny example: edges 0→1, 1→2, 2→0, 2→3. Pass 1 from 0 finishes 3 first, then 2, 1, 0; finish order [3, 2, 1, 0]. Reversed graph edges: 1→0, 2→1, 0→2, 3→2. Pass 2 starts at 0 (finished last): reaches 2 and 1 but not 3 (edge 3→2 points the wrong way) — SCC {0, 1, 2}. Then 3: SCC {3}.
How it works
- Pass 1: for every unvisited vertex run DFS on
G; when a vertex finishes (all neighbours explored), append it toorder. - Build
radj: for each edgeu → vinG, addutoradj[v]. - Pass 2: iterate
orderfrom the end (latest finisher first). For each vertex not yet assigned a component, run DFS onradj, assigning every reached vertex the current component id, then increment the id. - Return
comp[v](ids are in topological order of the condensation) and/or the list of components.
Why it works
Key lemma: if there is an edge from SCC A to SCC B in G, then the maximum finish time in A is larger than the maximum finish time in B. Either DFS enters A first and then finishes all of B inside that call (so A's root finishes later), or it enters B first and cannot reach A (no B → A path, else they would be one SCC), so all of B finishes before A is even discovered.
Therefore the vertex with the largest finish time overall lies in a source SCC of the condensation. In G^T that SCC is a sink, so DFS from that vertex reaches its own SCC (mutual reachability is preserved by reversal) and nothing else.
Induction: after removing the found SCC, the largest remaining finish time again identifies a source among the remaining components, and the reversed DFS again stays inside it because previously found components are marked visited.
Recognition
How to tell a problem wants this.
- Directed graph, need SCCs, and you prefer two simple DFS routines over low-link bookkeeping.
- Components must come out in topological order (sources first) — Kosaraju gives that directly.
- You already need the reversed graph for something else (e.g. "which vertices can reach
t").
Interactive visualization
Play, step, change the input. ← → and space work too.
1order = []; visited = {}2for u in nodes: if u unvisited: dfs1(u) # append u to order after its neighbors3GT = transpose(G) # reverse every edge4visited = {}5for u in reversed(order):6 if u unvisited in GT: dfs2(u) collects one SCC7return SCCsPseudocode
1order = []; visited[*] = false2dfs1(u): visited[u] = true; for v in adj[u]: if !visited[v]: dfs1(v); order.append(u)3for u in 0..n-1: if !visited[u]: dfs1(u)4radj = reverse of adj5comp[*] = -1; c = 06dfs2(u): comp[u] = c; for v in radj[u]: if comp[v] == -1: dfs2(v)7for u in reversed(order): if comp[u] == -1: dfs2(u); c += 18return comp # c components, ids in topological orderImplementations
1def kosaraju(n: int, adj: list[list[int]]) -> list[int]:2 """Return comp[v] = SCC id of v. Ids are in topological order of the3 condensation. Both passes are iterative, so deep graphs are safe."""41 · Pass 1: iterative post-order records the finish order5 visited = [False] * n6 order: list[int] = []7 for s in range(n):8 if visited[s]:9 continue10 visited[s] = True11 frames = [(s, 0)] # (vertex, next neighbour index)12 while frames:13 u, i = frames[-1]14 if i < len(adj[u]):15 frames[-1] = (u, i + 1)16 v = adj[u][i]17 if not visited[v]:18 visited[v] = True19 frames.append((v, 0))20 else:21 order.append(u)22 frames.pop()23 242 · Build the reversed graph25 radj: list[list[int]] = [[] for _ in range(n)]26 for u in range(n):27 for v in adj[u]:28 radj[v].append(u)29 303 · Pass 2: scan vertices in decreasing finish time31 comp = [-1] * n32 c = 033 for s in reversed(order):34 if comp[s] != -1:35 continue36 374 · Flood-fill one SCC on the reversed graph38 comp[s] = c39 stack = [s]40 while stack:41 u = stack.pop()42 for v in radj[u]:43 if comp[v] == -1:44 comp[v] = c45 stack.append(v)46 c += 147 return comp- Pass 1 keeps
(vertex, next_index)tuples; since tuples are immutable, advancing the cursor rewrites the top frame withframes[-1] = (u, i + 1). - Appending to
orderwhen the frame pops reproduces recursive post-order without recursion. radjis built with a list comprehension of independent lists, then one pass over all edges.for s in reversed(order)iterates lazily from the back — no copy of the list.- Each flood fill labels exactly one SCC;
ccounts components in condensation-topological order.
- Both passes are iterative, so
sys.setrecursionlimitis not needed; a recursive version dies at ~1000 frames by default and risks a C-stack crash even when raised. reversed(order)returns an iterator over the existing list — O(1) extra space, unlikeorder[::-1]which copies.- A mutable list frame (
[s, 0]withframes[-1][1] += 1) works too; the tuple version trades one allocation per step for immutability.
- Writing
u, i = frames[-1]and then mutating the locali— locals do not write back; you must reassignframes[-1]. - Using
order.pop()in pass 2 while also indexing it — pick one traversal style. - Building
radjas[[]] * n, aliasing a single list.
- Frame mutation differs: C++ mutates through structured-binding references, JS/TS mutate the array element
top[1]++, Python must reassign the tuple (or use a list frame) because tuples are immutable. - All four are fully iterative — unlike the Tarjan entry, no recursion-limit tuning is needed in any language.
- C++ returns
vector<int>; JS/TS returnnumber[]; Python returnslist[int]— all use comp-label form rather than lists of vertices, since Kosaraju is usually a preprocessing step.
Complexity
Extra O(V + E) memory for the reversed adjacency list; roughly 2× the work of Tarjan.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- You want the simplest provably correct SCC routine and can afford a second pass and a reversed copy of the graph.
- Components are needed in topological order (sources first).
- Iterative implementation is required — two independent DFS passes are easy to make stack-free.
- Memory-constrained settings where the reversed adjacency list is too expensive — use Tarjan's SCC Algorithm.
- You need low-link values anyway (e.g. also computing bridges) — Tarjan shares that machinery.
- Undirected graphs — use Connected Components.
Alternatives
Common mistakes
- Recording pre-order (discovery) instead of post-order (finish) in pass 1 — the ordering lemma only holds for finish times.
- Running pass 2 on the original graph instead of the reversed one, or iterating the finish order forwards instead of backwards.
- Building the reversed graph incorrectly for the adjacency-list representation (adding
vtoradj[u]instead ofutoradj[v]). - Recursive pass 1 overflowing the stack on long paths in Python/JavaScript — use the explicit
(vertex, nextIndex)stack.
Interview patterns
- Compute SCC ids, then answer "is the graph strongly connected?" (exactly one id).
- Condense and count source components to find the minimum number of starting vertices needed to reach everything.
- Explain the reverse-graph idea in words: "reversing keeps SCCs intact but flips the DAG of components, so the last finisher becomes a sink".
- Choosing between BFS, DFS, Dijkstra and DPAdvanced
- Stack versus queueBeginner
- Recursion versus iterationIntermediate
- When space complexity mattersIntermediate
- Course ScheduleIntermediate
- Number of IslandsIntermediate
- Word SearchAdvanced