Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 11 – Graphs: modelling, traversal and connectivity

Strongly connected components

Find the groups of a directed graph in which every vertex reaches every other, with two depth-first passes, and shrink them into a graph without cycles.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 9 of 30 minutes free
Free: A link graph and its components · Why the condensation has no cycles · Kosaraju's two passes, step by step · Exercise and self-check. With a pass: 3 in-depth parts and 3 interview answers, about 21 minutes.

What you will learn

  • Find SCCs with Kosaraju's two-pass algorithm
  • Find SCCs with Tarjan's low-link algorithm
  • Build and use the condensation DAG

Before you start

A strongly connected component (SCC) of a directed graph is a largest set of vertices in which every vertex can reach every other one by following edges in their direction. Every vertex lies in exactly one component, so the components split the vertices into disjoint groups; a vertex on no cycle is a component of its own. Shrink each component to a single vertex and the result, the condensation, has no cycles. It is a DAG, so a question about a tangled directed graph becomes a question about a DAG, where topological order applies. Two classic algorithms find every component in O(V + E) time. Kosaraju’s algorithm runs depth-first search twice, the second time over the reversed edges. Tarjan’s algorithm needs only one depth-first search, plus a stack and one more number for every vertex.

Ten pages of a site link to one another. Home, blog and archive link in a loop, so each of them reaches the other two; docs and api link to each other; shop, cart and checkout form a second loop; status and receipt sit on no loop at all.

Ten web pages linked one way, grouped into five boxes: three loops of pages and two single pages, linked downwards.Component 0Component 1Component 3Component 2Component 4homeblogarchivedocsapishopcartcheckoutstatusreceipt

Ten pages, five strongly connected components

Text description of the diagram

The figure shows ten pages and their links, with every strongly connected component drawn as a box.

  1. Component 0 holds home, blog and archive, which link in a loop: home to blog, blog to archive, archive to home.
  2. Component 1 holds docs and api, which link to each other.
  3. Component 3 holds shop, cart and checkout, in a loop: shop to cart, cart to checkout, checkout to shop.
  4. Component 2 holds status alone, and component 4 holds receipt alone.

The links between boxes all point one way: archive to docs, blog to shop, api to shop, docs to status, checkout to receipt and status to receipt. No link leads back from a lower box to a higher one, so the boxes themselves form a graph without cycles, the condensation. Its links are 0 to 1, 0 to 3, 1 to 2, 1 to 3, 2 to 4 and 3 to 4.

Archive links to docs, yet docs is not in the component of archive: nothing that leaves docs ever leads back to home, blog or archive. Reaching in one direction is not enough. The links between the boxes all point one way, from component 0 towards component 4.

Why the condensation has no cycles

Suppose component A could reach component B and B could reach A. Then every vertex of A would reach every vertex of B and back, so A and B together would be one larger set of mutually reachable vertices, and neither could be a largest one. So the links between components can never close a cycle, and the condensation is always a DAG. Every DAG has at least one source, a component that no other component reaches, and at least one sink, a component that reaches no other.

Kosaraju’s two passes, step by step

  1. Run depth-first search over the whole graph and record each vertex at the moment it finishes, when all its edges have been explored.
  2. Reverse every edge.
  3. Take the vertices in reverse finishing order. From each vertex not yet in a component, search the reversed graph; every vertex this search reaches that has no component yet belongs to the new component.

The vertex that finishes last always lies in a source component of the condensation. Reversing the edges turns that source into a sink, so a search from it in the reversed graph cannot leave its own component. The program below writes both passes with explicit stacks, so it never recurses:

Kosaraju's two passes on the ten pages Python · trace/kosaraju.py
# Kosaraju's algorithm on a small link graph of ten pages, with both passes written as loops (no recursion).
# Pass 1: depth-first search on the graph, recording each page when it finishes.
# Pass 2: depth-first search on the reversed graph, starting from pages in reverse finishing order; every search of
# this pass collects exactly one strongly connected component.

PAGES = ["home", "blog", "archive", "docs", "api", "shop", "cart", "checkout", "receipt", "status"]
LINKS = [(0, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 3), (4, 5), (1, 5), (5, 6), (6, 7), (7, 5), (7, 8), (3, 9), (9, 8)]


def finishing_order(n, adj):
    seen, order = [False] * n, []
    for s in range(n):
        if seen[s]:
            continue
        seen[s] = True
        stack = [(s, 0)]                   # (page, index of the next link to try)
        while stack:
            v, i = stack.pop()
            if i < len(adj[v]):
                stack.append((v, i + 1))
                w = adj[v][i]
                if not seen[w]:
                    seen[w] = True
                    stack.append((w, 0))
            else:
                order.append(v)            # every link of v is explored: v finishes
    return order


def kosaraju(n, links):
    adj, radj = [[] for _ in range(n)], [[] for _ in range(n)]
    for u, v in links:
        adj[u].append(v)
        radj[v].append(u)                  # the reversed graph
    order = finishing_order(n, adj)
    comp, components = [-1] * n, []
    for s in reversed(order):
        if comp[s] >= 0:
            continue
        comp[s], members, stack = len(components), [s], [s]
        while stack:
            v = stack.pop()
            for w in radj[v]:
                if comp[w] < 0:
                    comp[w] = len(components)
                    members.append(w)
                    stack.append(w)
        components.append(sorted(members))
    return order, comp, components


order, comp, components = kosaraju(len(PAGES), LINKS)
print("pass 1, pages as they finish:", " ".join(PAGES[v] for v in order))
for i, members in enumerate(components):
    print(f"pass 2, component {i}: {', '.join(PAGES[v] for v in members)}")
dag = sorted({(comp[u], comp[v]) for u, v in LINKS if comp[u] != comp[v]})
print("links between components:", ", ".join(f"{a} -> {b}" for a, b in dag))

Output

pass 1, pages as they finish: receipt checkout cart shop api status docs archive blog home
pass 2, component 0: home, blog, archive
pass 2, component 1: docs, api
pass 2, component 2: status
pass 2, component 3: shop, cart, checkout
pass 2, component 4: receipt
links between components: 0 -> 1, 0 -> 3, 1 -> 2, 1 -> 3, 2 -> 4, 3 -> 4

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 kosaraju.py

Receipt finishes first, since it links nowhere, and home finishes last. The second pass starts from home, collects home, blog and archive in the reversed graph and stops there, because no page outside that group links into it, so the reversed links offer no way out. The components come out in topological order of the condensation: every link between components runs from a lower number to a higher one.

Tarjan’s algorithm finds the same components in one search. It numbers the vertices as it discovers them, keeps for each the smallest number its subtree can reach through vertices still on a stack, and closes a component when a vertex can reach nothing older than itself.

Strongly connected components of a graph with V vertices and E edges
Operation Time Extra space
Kosaraju: two depth-first passes O(V + E) O(V + E): the reversed graph
Tarjan: one depth-first pass O(V + E) O(V): indexes, low-links, a stack
Condensation from the component ids O(V + E) O(V + E)

Exercise and self-check

Count the strongly connected components of a graph and the size of the largest, on graphs up to a cycle of 100,000 vertices.

Exercise · Hard · Python, JavaScript

Count the strongly connected components and the largest one

A directed graph has n vertices, numbered from 0, and a list of directed edges (u, v) (JavaScript: two-element arrays). Write scc_sizes(n, edges) (JavaScript: sccSizes) that returns two numbers: how many strongly connected components the graph has, and how many vertices the largest one holds. Python returns a tuple (count, largest), JavaScript an array [count, largest].

A vertex with no edges is a component of its own, and so is a vertex whose only edge is a loop to itself. Edges may repeat. The sample tests check the ten-page graph of the lesson, the small cases, random graphs against a slow reachability check, and a single cycle of 100,000 vertices, which needs a search that does not recurse once per vertex.

Python · Starter code · scc.py

def scc_sizes(n, edges):
    """(number of strongly connected components, number of vertices in the largest one)."""
    raise NotImplementedError("write scc_sizes")
The sample tests · test_scc.py
import random

from scc import scc_sizes

PAGES = [(0, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 3), (4, 5), (1, 5), (5, 6), (6, 7), (7, 5), (7, 8), (3, 9), (9, 8)]


def slow_sizes(n, edges):
    """Reachability from every vertex, then mutual reachability: right, and O(V (V + E))."""
    adj = [[] for _ in range(n)]
    for u, v in edges:
        adj[u].append(v)
    reach = []
    for s in range(n):
        seen, stack = {s}, [s]
        while stack:
            for w in adj[stack.pop()]:
                if w not in seen:
                    seen.add(w)
                    stack.append(w)
        reach.append(seen)
    groups = {frozenset(v for v in reach[s] if s in reach[v]) for s in range(n)}
    return len(groups), max((len(g) for g in groups), default=0)


def test_lesson_graph():
    """the ten pages of the lesson: five components, the largest of three pages"""
    assert scc_sizes(10, PAGES) == (5, 3)


def test_small_cases():
    """one vertex, a loop, a repeated edge, a chain and a two-way link"""
    assert scc_sizes(1, []) == (1, 1)
    assert scc_sizes(1, [(0, 0)]) == (1, 1)
    assert scc_sizes(2, [(0, 1), (0, 1)]) == (2, 1)
    assert scc_sizes(5, [(0, 1), (1, 2), (2, 3), (3, 4)]) == (5, 1)
    assert scc_sizes(3, [(0, 1), (1, 0)]) == (2, 2)


def test_random_against_reachability():
    """200 random graphs of up to 12 vertices, against a slow mutual-reachability check"""
    rng = random.Random(21)
    for _ in range(200):
        n = rng.randint(1, 12)
        edges = [(rng.randrange(n), rng.randrange(n)) for _ in range(rng.randint(0, 2 * n))]
        assert scc_sizes(n, edges) == slow_sizes(n, edges), (n, edges)


def test_long_cycle_and_long_path():
    """a single cycle of 100,000 vertices, and a path of 100,000 vertices"""
    n = 100_000
    assert scc_sizes(n, [(i, (i + 1) % n) for i in range(n)]) == (1, n)
    assert scc_sizes(n, [(i, i + 1) for i in range(n - 1)]) == (n, 1)

JavaScript · Starter code · scc.mjs

/** [number of strongly connected components, number of vertices in the largest one]. */
export function sccSizes(n, edges) {
  throw new Error('write sccSizes');
}
The sample tests · scc.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { sccSizes } from './scc.mjs';

const PAGES = [[0, 1], [1, 2], [2, 0], [2, 3], [3, 4], [4, 3], [4, 5], [1, 5], [5, 6], [6, 7], [7, 5], [7, 8], [3, 9], [9, 8]];

/** Reachability from every vertex, then mutual reachability: right, and O(V (V + E)). */
function slowSizes(n, edges) {
  const adj = Array.from({ length: n }, () => []);
  for (const [u, v] of edges) adj[u].push(v);
  const reach = [];
  for (let s = 0; s < n; s++) {
    const seen = new Set([s]);
    const stack = [s];
    while (stack.length) {
      for (const w of adj[stack.pop()]) {
        if (!seen.has(w)) {
          seen.add(w);
          stack.push(w);
        }
      }
    }
    reach.push(seen);
  }
  const groups = new Set();
  let largest = 0;
  for (let s = 0; s < n; s++) {
    const members = [...reach[s]].filter((v) => reach[v].has(s)).sort((a, b) => a - b);
    groups.add(members.join(','));
    largest = Math.max(largest, members.length);
  }
  return [groups.size, largest];
}

test('the ten pages of the lesson: five components, the largest of three pages', () => {
  assert.deepEqual(sccSizes(10, PAGES), [5, 3]);
});

test('one vertex, a loop, a repeated edge, a chain and a two-way link', () => {
  assert.deepEqual(sccSizes(1, []), [1, 1]);
  assert.deepEqual(sccSizes(1, [[0, 0]]), [1, 1]);
  assert.deepEqual(sccSizes(2, [[0, 1], [0, 1]]), [2, 1]);
  assert.deepEqual(sccSizes(5, [[0, 1], [1, 2], [2, 3], [3, 4]]), [5, 1]);
  assert.deepEqual(sccSizes(3, [[0, 1], [1, 0]]), [2, 2]);
});

test('200 random graphs of up to 12 vertices, against a slow mutual-reachability check', () => {
  let seed = 21;
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  const pick = (k) => Math.floor(random() * k);
  for (let t = 0; t < 200; t++) {
    const n = 1 + pick(12);
    const edges = Array.from({ length: pick(2 * n + 1) }, () => [pick(n), pick(n)]);
    assert.deepEqual(sccSizes(n, edges), slowSizes(n, edges), JSON.stringify([n, edges]));
  }
});

test('a single cycle of 100,000 vertices, and a path of 100,000 vertices', () => {
  const n = 100000;
  const cycle = [];
  const path = [];
  for (let i = 0; i < n; i++) cycle.push([i, (i + 1) % n]);
  for (let i = 0; i < n - 1; i++) path.push([i, i + 1]);
  assert.deepEqual(sccSizes(n, cycle), [1, n]);
  assert.deepEqual(sccSizes(n, path), [n, 1]);
});
A hint

Kosaraju's two passes are the easier ones to write without recursion. In the first pass, keep a stack of (vertex, position of the next edge) pairs and append a vertex to the finishing order when its edges run out. In the second pass, search the reversed graph from the vertices in reverse finishing order; every search that starts there collects one component, and any order of visiting works inside it.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 A directed graph has the vertices 0 to 5 and the edges 0 → 1, 1 → 2, 2 → 0, 2 → 3, 3 → 4 and 4 → 3. How many strongly connected components does it have?

    Type a number.

    Show the answer to question 1

    Answer: 3 components

    {0, 1, 2} is one loop and {3, 4} another, and vertex 5 has no edges, so it is a component of its own. The edge 2 → 3 joins the loops in one direction only, which is not enough to merge them.

  2. Question 2 of 4 Vertex a reaches vertex b, but b does not reach a. What follows?

    Choose one answer.

    Show the answer to question 2

    Answer: a and b are in different components, and the condensation has a path from a's component to b's

    A component needs reachability in both directions. One-way reachability puts a's component before b's in every topological order of the condensation.

  3. Question 3 of 4 In Kosaraju's algorithm, where does the second pass start, and over which edges does it search?

    Choose one answer.

    Show the answer to question 3

    Answer: From the vertex that finished last in the first pass, over the reversed edges

    The last vertex to finish lies in a source component. Reversing the edges makes that component a sink, so the search from it collects that component and nothing else.

  4. Question 4 of 4 Why can a problem on the condensation use topological order?

    Choose one answer.

    Show the answer to question 4

    Answer: Because two components that reached each other would be one component, so the condensation has no cycle

    A cycle among components would make all of them mutually reachable, contradicting that each component is a largest such set. A graph without cycles has a topological order, which the dynamic programming of the lesson relies on.

Why the second pass never leaks, proved, and Tarjan's low-links traced Pro: opens with a Pro, Premium or Ultimate pass

Kosaraju’s algorithm is short because one fact about finishing times does all the work. This part proves that fact, traces Tarjan’s single pass on the same ten pages and compares the two algorithms.

In this part:

  • The finishing-time lemma, and the proof of both passes
  • Tarjan’s low-links, traced on the ten pages
  • Kosaraju or Tarjan: the trade-offs

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

A cycle of 200,000 pages, Tarjan in five languages and four bugs reproduced Pro: opens with a Pro, Premium or Ultimate pass

On a single cycle of pages, a depth-first search keeps every page open at the same time, so a recursive version nests one call per page. This part reproduces that crash, runs Tarjan’s algorithm with an explicit stack in five languages, and reproduces four bugs on small graphs.

In this part:

  • Recursion on a long cycle, reproduced
  • Tarjan without recursion in five languages, with run records
  • Four bugs, each on a small graph
  • Edge cases worth a test

4 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Working on the condensation: seed pages, the longest walk, new links and a practice set Pro: opens with a Pro, Premium or Ultimate pass

Once the components are known, the condensation turns hard questions about a directed graph into easy ones on a DAG. This part computes three of them on the ten pages and on 100,000 random pages, and ends with a practice set.

In this part:

  • Seeds for a crawler and the longest walk, computed on the DAG
  • The fewest new links that make a graph strongly connected
  • A practice set, with worked answers

1 runnable example (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Why is the condensation of a directed graph, one vertex per strongly connected component, always acyclic?

    Because a cycle among components would merge them. If component A reached component B and B reached A, then any vertex of A could reach any vertex of B through that path, and get back the other way, so every vertex of A and B would reach every other. A and B together would then be a larger set of mutually reachable vertices, but a component is by definition the largest such set. So the links between components only ever point one way, the condensation is a DAG, and it always has a topological order, at least one source and at least one sink. That is what lets a question about a tangled graph be answered with DAG methods on its components.

  2. Mid to senior

    A financial model has 40,000 formula cells and 300,000 references, and its engine refuses to calculate while any circular reference exists. Users need every group of cells caught in a circle, and an estimate of the work to untangle each. What do you compute, how long does it take, and how do you rank the groups?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A link-analysis job finds strongly connected components with a recursive Tarjan in Python. It works on every test graph but crashes on the production link graph of 3 million pages. What is going on, and what are your options?

    Model answer, Pro. First step and follow-ups

  4. Mid to staff

    A crawler must reach every page of a site whose link graph has 2 million pages, starting from as few seed pages as possible. How many seeds do you need, how do you choose them, and what changes when new pages and links appear every hour?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A strongly connected component is a largest set of vertices that all reach one another; together they split the vertices.
  • The condensation, one vertex per component, never has a cycle, so DAG methods apply to it.
  • Kosaraju’s algorithm searches twice, the second time over reversed edges in reverse finishing order.
  • Tarjan’s algorithm finds the same components in one search with a stack and a low-link per vertex.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.