Arian Soleimanzadeh
  • Home
  • Blog
  • Podcasts
  • Videos
  • Contact
العربيةArabic
DeutschGerman
EnglishEnglish
فارسیPersian
한국어Korean
中文Chinese
Panel•Quick Contact

Languages

Choose your interface locale

ar

العربية

Arabic

de

Deutsch

German

en

English

English

fa

فارسی

Persian

ko

한국어

Korean

zh

中文

Chinese

Get Appointment

Drop a quick message — I’ll reply as soon as possible.

LinkedInFast response
Home/Articles/Implementing Kosaraju's Algorithm with JavaScript and TypeScript
KosarajuArticle

Implementing Kosaraju's Algorithm with JavaScript and TypeScript

Implement Kosaraju's algorithm from scratch with JavaScript and TypeScript. Learn graph representation, finishing order, graph transposition, SCC extraction, complexity analysis, testing, and production considerations.

August 20, 202612 min read1 Views
#Kosaraju#JavaScript#TypeScript#Graph Algorithms#Strongly Connected Components#SCC#DFS#Software Engineering

Arian Soleimanzadeh

Software Engineer & Researcher

Kosaraju algorithm implementation in JavaScript and TypeScript with DFS, transpose graph and strongly connected components

Arian Soleimanzadeh

AI · Code · Product

Research + Engineering
On this page
IntroductionKosaraju in Three StepsRepresenting the Graph in JavaScriptFirst DFS: Building the Finish OrderConstructing the Transpose GraphSecond DFS: Collecting ComponentsComplete JavaScript ImplementationA Generic TypeScript VersionReal Example: Microservice DependenciesAre Single-Vertex SCCs Valid?Time ComplexitySpace ComplexityRecursion Depth in JavaScriptCommon Mistake: Missing Sink VerticesCommon Mistake: Not Clearing `visited`Common Mistake: Using Discovery OrderCommon Mistake: Second DFS on the Original GraphA Reusable Graph ClassTesting SCC OutputJavaScript or TypeScript?When Is Kosaraju a Good Choice?Mental ModelConclusion

Introduction

Kosaraju's algorithm finds Strongly Connected Components (SCCs) in directed graphs. Understanding the theory is useful, but implementing the algorithm reveals why its two DFS passes and graph transpose work together.

In this article, we will implement Kosaraju using both JavaScript and TypeScript and examine the engineering details that matter beyond textbook pseudocode.

We will cover:

  • adjacency-list graph representation
  • the first DFS and finishing order
  • constructing the transpose graph
  • the second DFS
  • a generic TypeScript implementation
  • testing
  • time and space complexity
  • recursion limitations
  • common implementation mistakes

Kosaraju in Three Steps

At a high level:

Code
123
1. DFS on the original graph
2. Reverse every edge
3. DFS on the transpose using reverse finish order

Conceptually:

Code
1234567891011
Original Graph
      ↓
First DFS
      ↓
Finish Order
      ↓
Transpose Graph
      ↓
Second DFS
      ↓
SCCs

Consider:

Code
123456
0 → 1
1 → 2
2 → 0
1 → 3
3 → 4
4 → 3

The expected components are:

Code
12
{0, 1, 2}
{3, 4}

Representing the Graph in JavaScript

An adjacency list is a natural representation for sparse graphs:

JavaScript
1234567
const graph = new Map([
  [0, [1]],
  [1, [2, 3]],
  [2, [0]],
  [3, [4]],
  [4, [3]]
]);

Map is particularly useful because vertex identifiers do not have to be sequential integers.


First DFS: Building the Finish Order

The first DFS must add each vertex to the stack after all of its descendants have been processed.

JavaScript
1234567891011
function fillOrder(node, graph, visited, stack) {
  visited.add(node);

  for (const neighbor of graph.get(node) ?? []) {
    if (!visited.has(neighbor)) {
      fillOrder(neighbor, graph, visited, stack);
    }
  }

  stack.push(node);
}

The location of:

JavaScript
1
stack.push(node);

is critical.

We need finishing order rather than discovery order.

The mental model is:

Code
1234567
Enter vertex
   ↓
Explore descendants
   ↓
Finish vertex
   ↓
Push onto stack

Because a directed graph may be disconnected, DFS must be started from every still-unvisited vertex.


Constructing the Transpose Graph

The transpose of a directed graph reverses every edge.

Code
1
A → B

becomes:

Code
1
B → A

Implementation:

JavaScript
12345678910111213141516171819
function transposeGraph(graph) {
  const transpose = new Map();

  for (const node of graph.keys()) {
    transpose.set(node, []);
  }

  for (const [node, neighbors] of graph.entries()) {
    for (const neighbor of neighbors) {
      if (!transpose.has(neighbor)) {
        transpose.set(neighbor, []);
      }

      transpose.get(neighbor).push(node);
    }
  }

  return transpose;
}

The SCC membership itself is preserved when all edges are reversed.


Second DFS: Collecting Components

The second DFS runs on the transposed graph.

JavaScript
12345678910
function collectComponent(node, graph, visited, component) {
  visited.add(node);
  component.push(node);

  for (const neighbor of graph.get(node) ?? []) {
    if (!visited.has(neighbor)) {
      collectComponent(neighbor, graph, visited, component);
    }
  }
}

Each new DFS started according to the finishing stack identifies one SCC.


Complete JavaScript Implementation

JavaScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
function kosaraju(graph) {
  const visited = new Set();
  const stack = [];

  function fillOrder(node) {
    visited.add(node);

    for (const neighbor of graph.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        fillOrder(neighbor);
      }
    }

    stack.push(node);
  }

  for (const node of graph.keys()) {
    if (!visited.has(node)) {
      fillOrder(node);
    }
  }

  const transpose = new Map();

  for (const node of graph.keys()) {
    transpose.set(node, []);
  }

  for (const [node, neighbors] of graph.entries()) {
    for (const neighbor of neighbors) {
      if (!transpose.has(neighbor)) {
        transpose.set(neighbor, []);
      }

      transpose.get(neighbor).push(node);
    }
  }

  visited.clear();

  const components = [];

  function collectComponent(node, component) {
    visited.add(node);
    component.push(node);

    for (const neighbor of transpose.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        collectComponent(neighbor, component);
      }
    }
  }

  while (stack.length > 0) {
    const node = stack.pop();

    if (!visited.has(node)) {
      const component = [];
      collectComponent(node, component);
      components.push(component);
    }
  }

  return components;
}

Test it with:

JavaScript
123456789
const graph = new Map([
  [0, [1]],
  [1, [2, 3]],
  [2, [0]],
  [3, [4]],
  [4, [3]]
]);

console.log(kosaraju(graph));

A possible result is:

JSON
1234
[
  [0, 2, 1],
  [3, 4]
]

The order inside an SCC is not important.


A Generic TypeScript Version

TypeScript allows us to describe the graph more precisely:

TypeScript
1
type Graph<T> = Map<T, T[]>;

Now the implementation can support numbers, strings, or domain-specific identifiers.

TypeScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
type Graph<T> = Map<T, T[]>;

function kosaraju<T>(graph: Graph<T>): T[][] {
  const visited = new Set<T>();
  const stack: T[] = [];

  const fillOrder = (node: T): void => {
    visited.add(node);

    for (const neighbor of graph.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        fillOrder(neighbor);
      }
    }

    stack.push(node);
  };

  for (const node of graph.keys()) {
    if (!visited.has(node)) {
      fillOrder(node);
    }
  }

  const transpose: Graph<T> = new Map();

  for (const node of graph.keys()) {
    transpose.set(node, []);
  }

  for (const [node, neighbors] of graph.entries()) {
    for (const neighbor of neighbors) {
      if (!transpose.has(neighbor)) {
        transpose.set(neighbor, []);
      }

      transpose.get(neighbor)!.push(node);
    }
  }

  visited.clear();

  const components: T[][] = [];

  const collectComponent = (
    node: T,
    component: T[]
  ): void => {
    visited.add(node);
    component.push(node);

    for (const neighbor of transpose.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        collectComponent(neighbor, component);
      }
    }
  };

  while (stack.length > 0) {
    const node = stack.pop()!;

    if (!visited.has(node)) {
      const component: T[] = [];
      collectComponent(node, component);
      components.push(component);
    }
  }

  return components;
}

Real Example: Microservice Dependencies

Consider this architecture:

Code
12345678
user → payment
payment → notification
notification → user

report → analytics
analytics → report

admin → user

We can model it directly:

TypeScript
12345678910
const services: Graph<string> = new Map([
  ["user", ["payment"]],
  ["payment", ["notification"]],
  ["notification", ["user"]],
  ["report", ["analytics"]],
  ["analytics", ["report"]],
  ["admin", ["user"]]
]);

console.log(kosaraju(services));

Conceptually, the algorithm discovers:

Code
123
["user", "payment", "notification"]
["report", "analytics"]
["admin"]

The first two groups expose circular dependencies.

This is one way a theoretical graph algorithm can become an architecture-analysis tool.


Are Single-Vertex SCCs Valid?

Yes.

If a vertex does not belong to a larger strongly connected group, it forms an SCC by itself.

For:

Code
1
A → B

with no return path, the SCCs are:

Code
12
{A}
{B}

If your application only wants dependency cycles, you may filter components with more than one member.

TypeScript
123
const cycles = kosaraju(services).filter(
  component => component.length > 1
);

However, remember that a single vertex may contain a self-loop:

Code
1
A → A

which is also a cycle.


Time Complexity

The major operations are:

Code
123
First DFS       O(V + E)
Transpose       O(V + E)
Second DFS      O(V + E)

Therefore:

Code
1
Total = O(V + E)

The constant number of linear passes does not change the asymptotic complexity.


Space Complexity

We store:

  • the original adjacency list
  • the transpose adjacency list
  • visited state
  • finishing stack
  • result components

The overall auxiliary structure remains on the order of:

Code
1
O(V + E)

Recursion Depth in JavaScript

Recursive DFS is elegant and easy to understand, but JavaScript engines have limited call-stack depth.

A graph such as:

Code
1
1 → 2 → 3 → 4 → ... → 500000

can eventually cause:

Code
1
RangeError: Maximum call stack size exceeded

For very large or adversarial graphs, an iterative DFS implementation is safer.

A normal iterative DFS looks like:

TypeScript
12345678910111213141516171819202122232425
function dfsIterative<T>(
  start: T,
  graph: Graph<T>
): Set<T> {
  const visited = new Set<T>();
  const stack: T[] = [start];

  while (stack.length > 0) {
    const node = stack.pop()!;

    if (visited.has(node)) {
      continue;
    }

    visited.add(node);

    for (const neighbor of graph.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        stack.push(neighbor);
      }
    }
  }

  return visited;
}

Kosaraju's first pass is slightly more complicated because it requires true finishing order rather than ordinary traversal order.


Common Mistake: Missing Sink Vertices

Suppose the graph contains:

Code
1
A → B

If we only write:

JavaScript
1
graph.set("A", ["B"]);

then B might not exist as a key in the map.

A robust graph builder should register both endpoints:

JavaScript
12
graph.set("A", ["B"]);
graph.set("B", []);

Common Mistake: Not Clearing visited

After the first DFS, every processed vertex remains in the visited set.

Before the second pass:

JavaScript
1
visited.clear();

is mandatory.

Otherwise no SCCs will be explored during the second phase.


Common Mistake: Using Discovery Order

This is one of the most important implementation mistakes.

Incorrect:

JavaScript
12
visited.add(node);
stack.push(node);

Correct:

JavaScript
1234567
visited.add(node);

for (...) {
  ...
}

stack.push(node);

The algorithm needs post-order finishing information.


Common Mistake: Second DFS on the Original Graph

The second pass must traverse the transpose graph.

Running it on the original graph defeats the structural separation that Kosaraju relies on.


A Reusable Graph Class

For larger TypeScript applications, graph construction can be encapsulated:

TypeScript
12345678910111213141516171819
class DirectedGraph<T> {
  private adjacency = new Map<T, T[]>();

  addVertex(vertex: T): void {
    if (!this.adjacency.has(vertex)) {
      this.adjacency.set(vertex, []);
    }
  }

  addEdge(from: T, to: T): void {
    this.addVertex(from);
    this.addVertex(to);
    this.adjacency.get(from)!.push(to);
  }

  getAdjacencyList(): Graph<T> {
    return this.adjacency;
  }
}

This makes graph construction safer and easier to reuse.


Testing SCC Output

SCC ordering is not usually deterministic enough to compare raw arrays directly.

A normalization helper can sort both components and their members before assertions:

TypeScript
1234567
const normalize = <T extends string | number>(
  components: T[][]
): string[] => {
  return components
    .map(component => [...component].sort().join(","))
    .sort();
};

This allows tests to verify set membership rather than accidental traversal order.


JavaScript or TypeScript?

The algorithmic logic is almost identical.

JavaScript is convenient for demonstrations and small scripts.

TypeScript becomes attractive in larger applications because it provides:

  • type safety
  • reusable generic graph types
  • safer refactoring
  • clearer APIs
  • earlier detection of mistakes

For a production dependency analyzer or architecture tool, TypeScript is often the better fit.


When Is Kosaraju a Good Choice?

Kosaraju is appropriate when:

  • the input is a directed graph
  • every SCC must be discovered
  • implementation clarity matters
  • storing or generating a transpose graph is acceptable
  • linear O(V + E) complexity meets the requirements

When additional memory for the transpose is undesirable, algorithms such as Tarjan may also be worth considering.


Mental Model

A compact way to remember Kosaraju is:

Code
123456789101112131415
First DFS:
Find the finishing order

        ↓

Reverse all edges

        ↓

Second DFS:
Follow reverse finish order

        ↓

Each DFS tree reveals one SCC

Conclusion

We implemented Kosaraju's algorithm with both JavaScript and TypeScript.

The essential steps are:

  1. represent the directed graph with an adjacency list
  2. run DFS and record finishing order
  3. build the transpose graph
  4. reset visited state
  5. process vertices in reverse finishing order
  6. collect one SCC per DFS traversal

The algorithm runs in:

Code
1
O(V + E)

time with adjacency lists and requires linear graph storage.

More importantly, the implementation can be applied directly to practical problems such as detecting circular dependencies between modules, packages, or microservices.

The key implementation idea is:

The first DFS determines the correct processing order, the transpose reverses reachability between SCCs, and the second DFS exposes the component boundaries.

A natural next step is to compare this implementation with Tarjan's algorithm or build an iterative production-oriented version that avoids JavaScript recursion limits.

On this page
IntroductionKosaraju in Three StepsRepresenting the Graph in JavaScriptFirst DFS: Building the Finish OrderConstructing the Transpose GraphSecond DFS: Collecting ComponentsComplete JavaScript ImplementationA Generic TypeScript VersionReal Example: Microservice DependenciesAre Single-Vertex SCCs Valid?Time ComplexitySpace ComplexityRecursion Depth in JavaScriptCommon Mistake: Missing Sink VerticesCommon Mistake: Not Clearing `visited`Common Mistake: Using Discovery OrderCommon Mistake: Second DFS on the Original GraphA Reusable Graph ClassTesting SCC OutputJavaScript or TypeScript?When Is Kosaraju a Good Choice?Mental ModelConclusion

Article details

Publication metadata, reading time and live view information.

Published

August 20, 2026

Updated

August 19, 2026

Reading time

12 min read

Views

1

Author

Arian Soleimanzadeh

Let’s build something clean, fast, and beautiful.

Quick contact for collaborations, consulting, or product work.

Quick ContactEmail Me
Arian Soleimanzadeh

Personal portfolio focused on modern web engineering, UI systems, and practical AI products — clean code, clean design.

Quick Links

  • About
  • Blog
  • Projects
  • Contact

Contact

  • info@ariansoleimanzadeh.site
  • soleimanzadeh.a.work@gmail.com

Availability: Weekdays

Typically replies within 24h.

Newsletter

Get updates on posts, projects, and new releases.

© 2026 ariansoleimanzadeh.site — All rights reserved.

LinkedIn