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.

Coding Interview Patterns  Module 23 – Weighted graphs: shortest paths and spanning trees

Strongly connected components: Kosaraju and Tarjan

Districts of one-way streets in which every junction reaches every other: strongly connected components, with the brute force and the signal.

  • Advanced
  • 40 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 intro, Pro: 8 of 40 minutes free
Free: The problem: districts of one-way streets · The brute force, and why it fails at 100,000 junctions · What to notice. With a pass: the other 16 sections and 3 interview answers, about 32 minutes.

What you will learn

  • Find strongly connected components with Kosaraju's two passes or Tarjan's single pass, both in O(n + m)
  • Prove that Kosaraju's second pass never leaks from one component into another
  • Build the condensation and use its sources and sinks to count the fewest new edges for strong connectivity
  • Solve reachability and path questions with dynamic programming over the condensation, including 2-SAT
  • Write both searches with stacks of their own, so that a chain of 100,000 nodes cannot overflow the call stack

Before you start

What you’ll learn · Before you start

What you will learn

  • Find strongly connected components with Kosaraju's two passes or Tarjan's single pass, both in O(n + m)
  • Prove that Kosaraju's second pass never leaks from one component into another
  • Build the condensation and use its sources and sinks to count the fewest new edges for strong connectivity
  • Solve reachability and path questions with dynamic programming over the condensation, including 2-SAT
  • Write both searches with stacks of their own, so that a chain of 100,000 nodes cannot overflow the call stack

Before you start

On this page

The strongly connected components of a directed graph are its largest groups of nodes that can all reach one another along the edge directions. Two linear-time methods find them. Kosaraju’s algorithm runs two depth-first searches, the second over the reversed edges in reverse finishing order. Tarjan’s algorithm runs a single depth-first search with a stack and low-links. Shrinking each component to one node leaves a graph without cycles, the condensation.

The problem: districts of one-way streets

A city has n junctions, numbered 0 to n − 1, and m one-way streets; street (a, b) leads from junction a to a different junction b, and a street may be listed twice. A district is a largest group of junctions in which every junction can drive to every other, following streets the way they point. (A) How many districts are there, and which district holds each junction? (B) The city may open new one-way streets: what is the fewest number that lets every junction reach every other? Limits: 1 ≤ n ≤ 100,000 and 0 ≤ m ≤ 200,000.

  • Nine junctions with streets 0→1, 1→2, 2→0, 2→3, 3→4, 4→5, 5→3, 5→6, 6→7, 7→6 and 8→4 form four districts, {0, 1, 2}, {3, 4, 5}, {6, 7} and {8}, and need 2 new streets.
  • One junction is one district and needs none.
  • Two junctions with the street 0→1 are two districts and need 1, the street 1→0.
Nine junctions joined by one-way streets, grouped into four districts: {0, 1, 2}, {3, 4, 5}, {6, 7} and {8}.Nine junctions, four districtsdistrict Adistrict BCD012345678a one-way streeta district: every junction reaches every other

The first example and its four districts

Text description of the diagram

Nine numbered junctions with one-way streets drawn as arrows, and four dashed boxes around the districts. District A holds junctions 0, 1 and 2, joined in a loop: 0 to 1, 1 to 2 and 2 to 0. An arrow leaves district A from junction 2 to junction 3.

District B holds junctions 3, 4 and 5, joined in a second loop: 3 to 4, 4 to 5 and 5 to 3. An arrow leaves district B from junction 5 to junction 6. District C holds junctions 6 and 7, with one street each way between them. District D, at the bottom, holds junction 8 alone, with a single street from 8 into junction 4 of district B. Inside each box every junction can reach every other; between boxes, streets lead only from A and D into B, and from B into C. A legend explains the arrows and the dashed boxes.

The brute force, and why it fails at 100,000 junctions

Search from every junction, and put two junctions in one district when each reaches the other. For part B, try every set of k new streets, for k = 1, 2 and so on, until the city is one district:

A search from every junction, and every set of new streets Python · brute/brute_force.py
"""The brute force for districts of one-way streets: a search from every junction, then a pair of junctions shares a
district when each reaches the other. For the fewest new streets, try every set of k new streets for k = 1, 2, ..."""

from itertools import combinations


def reach_from(n, near, s):
    seen, todo = {s}, [s]
    while todo:
        for v in near[todo.pop()]:
            if v not in seen:
                seen.add(v)
                todo.append(v)
    return seen


def districts(n, streets):
    near = [[] for _ in range(n)]
    for a, b in streets:
        near[a].append(b)
    reach = [reach_from(n, near, s) for s in range(n)]  # n searches, n * n answers kept
    label = [-1] * n
    count = 0
    for u in range(n):
        if label[u] == -1:
            for v in range(n):
                if v in reach[u] and u in reach[v]:
                    label[v] = count
            count += 1
    return count, label


def fewest_new_streets(n, streets):
    if districts(n, streets)[0] == 1:
        return 0, 0
    candidates = [(a, b) for a in range(n) for b in range(n) if a != b]
    tried = 0
    for k in range(1, n + 1):
        for extra in combinations(candidates, k):
            tried += 1
            if districts(n, streets + list(extra))[0] == 1:
                return k, tried


NINE = [(0, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 5), (5, 3), (5, 6), (6, 7), (7, 6), (8, 4)]
for name, n, streets in [("nine junctions", 9, NINE), ("one junction", 1, []), ("two junctions", 2, [(0, 1)])]:
    count, label = districts(n, streets)
    k, tried = fewest_new_streets(n, streets)
    found = f"fewest new streets {k}, found after trying {tried:,} sets" if k else "no new street needed"
    print(f"{name}: {count} district{'s' * (count != 1)} {label}; {found}")

n, m = 100_000, 200_000
print(f"{n:,} junctions: {n:,} searches of up to {n + m:,} steps, {n * (n + m):,} in all, and {n * n:,} reach answers")
print(f"new streets at {n:,} junctions: {n * (n - 1):,} candidates for a single street alone")

Output

nine junctions: 4 districts [0, 0, 0, 1, 1, 1, 2, 2, 3]; fewest new streets 2, found after trying 589 sets
one junction: 1 district [0]; no new street needed
two junctions: 2 districts [0, 1]; fewest new streets 1, found after trying 2 sets
100,000 junctions: 100,000 searches of up to 300,000 steps, 30,000,000,000 in all, and 10,000,000,000 reach answers
new streets at 100,000 junctions: 9,999,900,000 candidates for a single street alone

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

It finds four districts and 2 new streets after trying 589 sets. At the limits, though, that is 100,000 searches of up to 300,000 steps, 3 × 10¹⁰ in all, and 10¹⁰ reach answers to keep, about 1.25 GB even at one bit each. Part B is worse: a single new street already has 9,999,900,000 candidates.

What to notice

  • Reaching each other is an equivalence. If u and v reach each other, and so do v and w, then u and w do too, so the districts split the junctions into groups that never overlap.
  • Between districts, streets go one way. Streets leading from district X to Y and also back would make X and Y one district, so the graph of districts has no cycle.
  • Part B lives on that graph. It depends on the sources, districts that no street enters, and the sinks, districts that no street leaves.

Three methods follow:

  • Kosaraju’s two passes record the order in which a search finishes the junctions, then search the reversed streets, latest finisher first, collecting one district per start.
  • Tarjan’s single pass keeps the current search’s junctions on a stack and closes a district when a junction’s low-link equals its own number.
  • The condensation has one node per district and an arrow wherever a street joins two districts.

Kosaraju’s two passes, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why the second pass never leaks into another district, proved
  • Tarjan’s single pass with low-links
  • A dry run on the nine junctions
  • The condensation and the fewest new streets, proved
  • Both searches with stacks of their own
  • The code in five languages
  • Kosaraju or Tarjan: the trade-offs
  • How much work it does
  • Edge cases and common bugs
  • Variants: the longest drive and 2-SAT
  • Follow-up questions
  • Exercise: districts and sources
    • Count the districts and the districts nothing leads into
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Why does the graph of strongly connected components, the condensation, never contain a cycle?
    • A platform has 3,000 services and 40,000 dependencies of the form "A calls B". Releases must deploy a service after everything it calls, but some services call each other in cycles. How do you find the groups that must ship together and an order for the groups?
    • An airline's 600 airports are joined by one-way routes, and some airports cannot be reached from others. What is the fewest number of new one-way routes that lets every airport reach every other, and why can fewer not work?
    • A release has 200 feature flags, each on or off, and 1,500 rules such as "if A is on, B must be off" or "A or B must be on". Before each release you must say whether some setting satisfies every rule and produce one. How, in linear time?
  • Key takeaways

1 diagram · 5 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 26 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; nothing renews unless you choose to. 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).

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.