Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

Graph

83 exports Since v3.18.0 Source

Algorithms

Since v3.18.0

Find the shortest path between two nodes using A* pathfinding algorithm.

A* is an extension of Dijkstra's algorithm that uses a heuristic function to guide the search towards the target, potentially finding paths faster than Dijkstra's. The heuristic must be admissible (never overestimate the actual cost).

Signature

declare function astar<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: AstarConfig<E, N>): Option<PathResult<E>>

Example

import { Graph, Option } from "effect"
const graph = Graph.directed<{x: number, y: number}, number>((mutable) => {
const a = Graph.addNode(mutable, {x: 0, y: 0})
const b = Graph.addNode(mutable, {x: 1, y: 0})
const c = Graph.addNode(mutable, {x: 2, y: 0})
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, c, 1)
})
// Manhattan distance heuristic
const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
if (Option.isSome(result)) {
console.log(result.value.path) // [0, 1, 2] - shortest path
console.log(result.value.distance) // 2 - total distance
}

Find the shortest path between two nodes using Bellman-Ford algorithm.

Bellman-Ford algorithm can handle negative edge weights and detects negative cycles. It has O(VE) time complexity, slower than Dijkstra's but more versatile. Returns Option.none() if a negative cycle is detected that affects the path.

Signature

declare function bellmanFord<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: BellmanFordConfig<E>): Option<PathResult<E>>

Example

import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, -1) // Negative weight allowed
Graph.addEdge(mutable, b, c, 3)
Graph.addEdge(mutable, a, c, 5)
})
const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
if (Option.isSome(result)) {
console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
console.log(result.value.distance) // 2 - total distance
}

Find connected components in an undirected graph. Each component is represented as an array of node indices.

Signature

declare function connectedComponents<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<Array<number>>

Example

import { Graph } from "effect"
const graph = Graph.undirected<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
const d = Graph.addNode(mutable, "D")
Graph.addEdge(mutable, a, b, "edge") // Component 1: A-B
Graph.addEdge(mutable, c, d, "edge") // Component 2: C-D
})
const components = Graph.connectedComponents(graph)
console.log(components) // [[0, 1], [2, 3]]
Since v3.18.0

Find the shortest path between two nodes using Dijkstra's algorithm.

Dijkstra's algorithm works with non-negative edge weights and finds the shortest path from a source node to a target node in O((V + E) log V) time complexity.

Signature

declare function dijkstra<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: DijkstraConfig<E>): Option<PathResult<E>>

Example

import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 5)
Graph.addEdge(mutable, a, c, 10)
Graph.addEdge(mutable, b, c, 2)
})
const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
if (Option.isSome(result)) {
console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
console.log(result.value.distance) // 7 - total distance
}

Find shortest paths between all pairs of nodes using Floyd-Warshall algorithm.

Floyd-Warshall algorithm computes shortest paths between all pairs of nodes in O(Vยณ) time. It can handle negative edge weights and detect negative cycles.

Signature

declare function floydWarshall<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, cost: (edgeData: E) => number): AllPairsResult<E>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 3)
Graph.addEdge(mutable, b, c, 2)
Graph.addEdge(mutable, a, c, 7)
})
const result = Graph.floydWarshall(graph, (edgeData) => edgeData)
const distanceAToC = result.distances.get(0)?.get(2) // 5 (A->B->C)
const pathAToC = result.paths.get(0)?.get(2) // [0, 1, 2]
Since v3.18.0

Checks if the graph is acyclic (contains no cycles).

Uses depth-first search to detect back edges, which indicate cycles. For directed graphs, any back edge creates a cycle. For undirected graphs, a back edge that doesn't go to the immediate parent creates a cycle.

Signature

declare function isAcyclic<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): boolean

Example

import { Graph } from "effect"
// Acyclic directed graph (DAG)
const dag = Graph.directed<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, "A->B")
Graph.addEdge(mutable, b, c, "B->C")
})
console.log(Graph.isAcyclic(dag)) // true
// Cyclic directed graph
const cyclic = Graph.directed<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, "A->B")
Graph.addEdge(mutable, b, a, "B->A") // Creates cycle
})
console.log(Graph.isAcyclic(cyclic)) // false

Checks if an undirected graph is bipartite.

A bipartite graph is one whose vertices can be divided into two disjoint sets such that no two vertices within the same set are adjacent. Uses BFS coloring to determine bipartiteness.

Signature

declare function isBipartite<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): boolean

Example

import { Graph } from "effect"
// Bipartite graph (alternating coloring possible)
const bipartite = Graph.undirected<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
const d = Graph.addNode(mutable, "D")
Graph.addEdge(mutable, a, b, "edge") // Set 1: {A, C}, Set 2: {B, D}
Graph.addEdge(mutable, b, c, "edge")
Graph.addEdge(mutable, c, d, "edge")
})
console.log(Graph.isBipartite(bipartite)) // true
// Non-bipartite graph (odd cycle)
const triangle = Graph.undirected<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, "edge")
Graph.addEdge(mutable, b, c, "edge")
Graph.addEdge(mutable, c, a, "edge") // Triangle (3-cycle)
})
console.log(Graph.isBipartite(triangle)) // false

Find strongly connected components in a directed graph using Kosaraju's algorithm. Each SCC is represented as an array of node indices.

Signature

declare function stronglyConnectedComponents<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): Array<Array<number>>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, "A->B")
Graph.addEdge(mutable, b, c, "B->C")
Graph.addEdge(mutable, c, a, "C->A") // Creates SCC: A-B-C
})
const sccs = Graph.stronglyConnectedComponents(graph)
console.log(sccs) // [[0, 1, 2]]

Constructors

Since v3.18.0

Creates a directed graph, optionally with initial mutations.

Signature

declare function directed<N, E>(mutate?: (mutable: MutableDirectedGraph<N, E>) => void): DirectedGraph<N, E>

Example

import { Graph } from "effect"
// Directed graph with initial nodes and edges
const graph = Graph.directed<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, "A->B")
Graph.addEdge(mutable, b, c, "B->C")
})

Creates an undirected graph, optionally with initial mutations.

Signature

declare function undirected<N, E>(mutate?: (mutable: MutableUndirectedGraph<N, E>) => void): UndirectedGraph<N, E>

Example

import { Graph } from "effect"
// Undirected graph with initial nodes and edges
const graph = Graph.undirected<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, "A-B")
Graph.addEdge(mutable, b, c, "B-C")
})

Errors

Error thrown when a graph operation fails.

Signature

declare class GraphError extends YieldableError<this> & {
readonly _tag: "GraphError";
} & Readonly<{
readonly message: string;
}> {
constructor(args: {
readonly message: string;
});
}

Getters

Since v3.18.0

Returns the number of edges in the graph.

Signature

declare function edgeCount<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): number

Example

import { Graph } from "effect"
const emptyGraph = Graph.directed<string, number>()
console.log(Graph.edgeCount(emptyGraph)) // 0
const graphWithEdges = Graph.mutate(emptyGraph, (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const nodeC = Graph.addNode(mutable, "Node C")
Graph.addEdge(mutable, nodeA, nodeB, 1)
Graph.addEdge(mutable, nodeB, nodeC, 2)
Graph.addEdge(mutable, nodeC, nodeA, 3)
})
console.log(Graph.edgeCount(graphWithEdges)) // 3
Since v3.18.0

Finds the first edge that matches the given predicate.

Signature

declare function findEdge<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: E, source: number, target: number) => boolean): Option<number>

Example

import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const nodeC = Graph.addNode(mutable, "Node C")
Graph.addEdge(mutable, nodeA, nodeB, 10)
Graph.addEdge(mutable, nodeB, nodeC, 20)
})
const result = Graph.findEdge(graph, (data) => data > 15)
console.log(result) // Option.some(1)
const notFound = Graph.findEdge(graph, (data) => data > 100)
console.log(notFound) // Option.none()
Since v3.18.0

Finds all edges that match the given predicate.

Signature

declare function findEdges<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: E, source: number, target: number) => boolean): Array<number>

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const nodeC = Graph.addNode(mutable, "Node C")
Graph.addEdge(mutable, nodeA, nodeB, 10)
Graph.addEdge(mutable, nodeB, nodeC, 20)
Graph.addEdge(mutable, nodeC, nodeA, 30)
})
const result = Graph.findEdges(graph, (data) => data >= 20)
console.log(result) // [1, 2]
const empty = Graph.findEdges(graph, (data) => data > 100)
console.log(empty) // []
Since v3.18.0

Finds the first node that matches the given predicate.

Signature

declare function findNode<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: N) => boolean): Option<number>

Example

import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
Graph.addNode(mutable, "Node A")
Graph.addNode(mutable, "Node B")
Graph.addNode(mutable, "Node C")
})
const result = Graph.findNode(graph, (data) => data.startsWith("Node B"))
console.log(result) // Option.some(1)
const notFound = Graph.findNode(graph, (data) => data === "Node D")
console.log(notFound) // Option.none()
Since v3.18.0

Finds all nodes that match the given predicate.

Signature

declare function findNodes<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: N) => boolean): Array<number>

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
Graph.addNode(mutable, "Start A")
Graph.addNode(mutable, "Node B")
Graph.addNode(mutable, "Start C")
})
const result = Graph.findNodes(graph, (data) => data.startsWith("Start"))
console.log(result) // [0, 2]
const empty = Graph.findNodes(graph, (data) => data === "Not Found")
console.log(empty) // []
Since v3.18.0

Gets the edge data associated with an edge index, if it exists.

Signature

declare function getEdge<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, edgeIndex: number): Option<Edge<E>>

Example

import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
Graph.addEdge(mutable, nodeA, nodeB, 42)
})
const edgeIndex = 0
const edgeData = Graph.getEdge(graph, edgeIndex)
if (Option.isSome(edgeData)) {
console.log(edgeData.value.data) // 42
console.log(edgeData.value.source) // NodeIndex(0)
console.log(edgeData.value.target) // NodeIndex(1)
}
Since v3.18.0

Gets the data associated with a node index, if it exists.

Signature

declare function getNode<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Option<N>

Example

import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
Graph.addNode(mutable, "Node A")
})
const nodeIndex = 0
const nodeData = Graph.getNode(graph, nodeIndex)
if (Option.isSome(nodeData)) {
console.log(nodeData.value) // "Node A"
}
Since v3.18.0

Checks if an edge exists between two nodes in the graph.

Signature

declare function hasEdge<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, source: number, target: number): boolean

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const nodeC = Graph.addNode(mutable, "Node C")
Graph.addEdge(mutable, nodeA, nodeB, 42)
})
const nodeA = 0
const nodeB = 1
const nodeC = 2
const hasAB = Graph.hasEdge(graph, nodeA, nodeB)
console.log(hasAB) // true
const hasAC = Graph.hasEdge(graph, nodeA, nodeC)
console.log(hasAC) // false
Since v3.18.0

Checks if a node with the given index exists in the graph.

Signature

declare function hasNode<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): boolean

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
Graph.addNode(mutable, "Node A")
})
const nodeIndex = 0
const exists = Graph.hasNode(graph, nodeIndex)
console.log(exists) // true
const nonExistentIndex = 999
const notExists = Graph.hasNode(graph, nonExistentIndex)
console.log(notExists) // false
Since v3.18.0

Returns the neighboring nodes (targets of outgoing edges) for a given node.

Signature

declare function neighbors<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Array<number>

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const nodeC = Graph.addNode(mutable, "Node C")
Graph.addEdge(mutable, nodeA, nodeB, 1)
Graph.addEdge(mutable, nodeA, nodeC, 2)
})
const nodeA = 0
const nodeB = 1
const nodeC = 2
const neighborsA = Graph.neighbors(graph, nodeA)
console.log(neighborsA) // [NodeIndex(1), NodeIndex(2)]
const neighborsB = Graph.neighbors(graph, nodeB)
console.log(neighborsB) // []
Since v3.18.0

Returns the number of nodes in the graph.

Signature

declare function nodeCount<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): number

Example

import { Graph } from "effect"
const emptyGraph = Graph.directed<string, number>()
console.log(Graph.nodeCount(emptyGraph)) // 0
const graphWithNodes = Graph.mutate(emptyGraph, (mutable) => {
Graph.addNode(mutable, "Node A")
Graph.addNode(mutable, "Node B")
Graph.addNode(mutable, "Node C")
})
console.log(Graph.nodeCount(graphWithNodes)) // 3

Iterators

Since v3.18.0

Creates a new BFS iterator with optional configuration.

The iterator maintains a queue of nodes to visit and tracks discovered nodes. It provides lazy evaluation of the breadth-first search.

Signature

declare function bfs<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SearchConfig): NodeWalker<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, c, 1)
})
// Start from a specific node
const bfs1 = Graph.bfs(graph, { start: [0] })
for (const nodeIndex of Graph.indices(bfs1)) {
console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
}
// Empty iterator (no starting nodes)
const bfs2 = Graph.bfs(graph)
// Can be used programmatically
Since v3.18.0

Creates a new DFS iterator with optional configuration.

The iterator maintains a stack of nodes to visit and tracks discovered nodes. It provides lazy evaluation of the depth-first search.

Signature

declare function dfs<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SearchConfig): NodeWalker<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, c, 1)
})
// Start from a specific node
const dfs1 = Graph.dfs(graph, { start: [0] })
for (const nodeIndex of Graph.indices(dfs1)) {
console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
}
// Empty iterator (no starting nodes)
const dfs2 = Graph.dfs(graph)
// Can be used programmatically

Creates a new DFS postorder iterator with optional configuration.

The iterator maintains a stack with visit state tracking and emits nodes in postorder (after all descendants have been processed). Essential for dependency resolution and tree destruction algorithms.

Signature

declare function dfsPostOrder<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SearchConfig): NodeWalker<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const root = Graph.addNode(mutable, "root")
const child1 = Graph.addNode(mutable, "child1")
const child2 = Graph.addNode(mutable, "child2")
Graph.addEdge(mutable, root, child1, 1)
Graph.addEdge(mutable, root, child2, 1)
})
// Postorder: children before parents
const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
for (const node of postOrder) {
console.log(node) // 1, 2, 0
}
Since v3.18.0

Creates an iterator over all edge indices in the graph.

The iterator produces edge indices in the order they were added to the graph. This provides access to all edges regardless of connectivity.

Signature

declare function edges<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): EdgeWalker<E>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, c, 2)
})
const indices = Array.from(Graph.indices(Graph.edges(graph)))
console.log(indices) // [0, 1]
Since v3.18.0

Creates an iterator over external nodes (nodes without edges in specified direction).

External nodes are nodes that have no outgoing edges (direction="outgoing") or no incoming edges (direction="incoming"). These are useful for finding sources, sinks, or isolated nodes.

Signature

declare function externals<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: ExternalsConfig): NodeWalker<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const source = Graph.addNode(mutable, "source") // 0 - no incoming
const middle = Graph.addNode(mutable, "middle") // 1 - has both
const sink = Graph.addNode(mutable, "sink") // 2 - no outgoing
const isolated = Graph.addNode(mutable, "isolated") // 3 - no edges
Graph.addEdge(mutable, source, middle, 1)
Graph.addEdge(mutable, middle, sink, 2)
})
// Nodes with no outgoing edges (sinks + isolated)
const sinks = Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" })))
console.log(sinks) // [2, 3]
// Nodes with no incoming edges (sources + isolated)
const sources = Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" })))
console.log(sources) // [0, 3]
Since v3.18.0

Creates an iterator over all node indices in the graph.

The iterator produces node indices in the order they were added to the graph. This provides access to all nodes regardless of connectivity.

Signature

declare function nodes<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): NodeWalker<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 1)
})
const indices = Array.from(Graph.indices(Graph.nodes(graph)))
console.log(indices) // [0, 1, 2]
Since v3.18.0

Creates a new topological sort iterator with optional configuration.

The iterator uses Kahn's algorithm to lazily produce nodes in topological order. Throws an error if the graph contains cycles.

Signature

declare function topo<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: TopoConfig): NodeWalker<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, c, 1)
})
// Standard topological sort
const topo1 = Graph.topo(graph)
for (const nodeIndex of Graph.indices(topo1)) {
console.log(nodeIndex) // 0, 1, 2 (topological order)
}
// With initial nodes
const topo2 = Graph.topo(graph, { initials: [0] })
// Throws error for cyclic graph
const cyclicGraph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, a, 2) // Creates cycle
})
try {
Graph.topo(cyclicGraph) // Throws: "Cannot perform topological sort on cyclic graph"
} catch (error) {
console.log((error as Error).message)
}

Models

Since v3.18.0

Result of all-pairs shortest path computation.

Signature

interface AllPairsResult<E> {
readonly costs: Map<number, Map<number, Array<E>>>;
readonly distances: Map<number, Map<number, number>>;
readonly paths: Map<number, Map<number, Array<number> | null>>;
}

AstarConfig

interface
Since v3.18.0

Configuration for A* algorithm.

Signature

interface AstarConfig<E, N> {
cost: (edgeData: E) => number;
heuristic: (sourceNodeData: N, targetNodeData: N) => number;
source: number;
target: number;
}
Since v3.18.0

Configuration for Bellman-Ford algorithm.

Signature

interface BellmanFordConfig<E> {
cost: (edgeData: E) => number;
source: number;
target: number;
}
Since v3.18.0

Configuration for Dijkstra's algorithm.

Signature

interface DijkstraConfig<E> {
cost: (edgeData: E) => number;
source: number;
target: number;
}
Since v3.18.0

Directed graph type alias.

Signature

type DirectedGraph<N, E> = Graph<N, E, "directed">
Since v3.18.0

Direction for graph traversal, indicating which edges to follow.

Signature

type Direction = "outgoing" | "incoming"

Example

import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, "A->B")
})
// Follow outgoing edges (normal direction)
const outgoingNodes = Array.from(Graph.indices(Graph.dfs(graph, { start: [0], direction: "outgoing" })))
// Follow incoming edges (reverse direction)
const incomingNodes = Array.from(Graph.indices(Graph.dfs(graph, { start: [1], direction: "incoming" })))
Since v3.18.0

Edge data containing source, target, and user data.

Signature

declare class Edge<E> extends Class<{
readonly data: E;
readonly source: NodeIndex;
readonly target: NodeIndex;
}> {
constructor<E>(args: {
readonly data: E;
readonly source: number;
readonly target: number;
});
}
Since v3.18.0

Edge index for edge identification using plain numbers.

Signature

type EdgeIndex = number
Since v3.18.0

Type alias for edge iteration using Walker. EdgeWalker is represented as Walker<EdgeIndex, Edge>.

Signature

type EdgeWalker<E> = Walker<EdgeIndex, Edge<E>>
Since v3.18.0

Configuration for externals iterator.

Signature

interface ExternalsConfig {
readonly direction?: Direction;
}

Graph

interface
Since v3.18.0

Immutable graph interface.

Signature

interface Graph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> {
readonly mutable: false;
readonly type: T;
}
Since v3.18.0

Configuration options for GraphViz DOT format generation from graphs.

Signature

interface GraphVizOptions<N, E> {
readonly edgeLabel?: (data: E) => string;
readonly graphName?: string;
readonly nodeLabel?: (data: N) => string;
}

Kind

type
Since v3.18.0

Graph type for distinguishing directed and undirected graphs.

Signature

type Kind = "directed" | "undirected"

Mermaid diagram type.

Signature

type MermaidDiagramType = "flowchart" | "graph"
Since v3.18.0

Mermaid diagram direction types.

Signature

type MermaidDirection = "TB" | "TD" | "BT" | "LR" | "RL"
Since v3.18.0

Mermaid node shape types.

Signature

type MermaidNodeShape = "rectangle" | "rounded" | "circle" | "diamond" | "hexagon" | "stadium" | "subroutine" | "cylindrical"
Since v3.18.0

Configuration options for Mermaid diagram generation.

Signature

interface MermaidOptions<N, E> {
readonly diagramType?: MermaidDiagramType;
readonly direction?: MermaidDirection;
readonly edgeLabel?: (data: E) => string;
readonly nodeLabel?: (data: N) => string;
readonly nodeShape?: (data: N) => MermaidNodeShape;
}

Mutable directed graph type alias.

Signature

type MutableDirectedGraph<N, E> = MutableGraph<N, E, "directed">

MutableGraph

interface
Since v3.18.0

Mutable graph interface.

Signature

interface MutableGraph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> {
readonly mutable: true;
readonly type: T;
}

Mutable undirected graph type alias.

Signature

type MutableUndirectedGraph<N, E> = MutableGraph<N, E, "undirected">
Since v3.18.0

Node index for node identification using plain numbers.

Signature

type NodeIndex = number
Since v3.18.0

Type alias for node iteration using Walker. NodeWalker is represented as Walker<NodeIndex, N>.

Signature

type NodeWalker<N> = Walker<NodeIndex, N>

PathResult

interface
Since v3.18.0

Result of a shortest path computation containing the path and total distance.

Signature

interface PathResult<E> {
readonly costs: Array<E>;
readonly distance: number;
readonly path: Array<number>;
}

Proto

interface
Since v3.18.0

Graph prototype interface.

Signature

interface Proto<out N, out E> extends Iterable<readonly [NodeIndex, N]>, Equal, Pipeable, Inspectable {
readonly "~effect/Graph": "~effect/Graph";
readonly adjacency: Map<number, Array<number>>;
readonly edges: Map<number, Edge<E>>;
isAcyclic: Option<boolean>;
nextEdgeIndex: number;
nextNodeIndex: number;
readonly nodes: Map<number, N>;
readonly reverseAdjacency: Map<number, Array<number>>;
}

SearchConfig

interface
Since v3.18.0

Configuration for graph search iterators.

Signature

interface SearchConfig {
readonly direction?: Direction;
readonly start?: Array<number>;
}

TopoConfig

interface
Since v3.18.0

Configuration options for topological sort iterator.

Signature

interface TopoConfig {
readonly initials?: Array<number>;
}
Since v3.18.0

Undirected graph type alias.

Signature

type UndirectedGraph<N, E> = Graph<N, E, "undirected">
Since v3.18.0

Concrete class for iterables that produce [NodeIndex, NodeData] tuples.

This class provides a common abstraction for all iterables that return node data, including traversal iterators (DFS, BFS, etc.) and element iterators (nodes, externals). It uses a mapEntry function pattern for flexible iteration and transformation.

Signature

declare class Walker<T, N> implements Iterable<[T, N]> {
constructor<T, N>(visit: <U>(f: (index: T, data: N) => U) => Iterable<U>);
readonly [iterator]: () => Iterator<[T, N]>;
readonly visit: <U>(f: (index: T, data: N) => U) => Iterable<U>;
}

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, 1)
})
// Both traversal and element iterators return NodeWalker
const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
// Common interface for working with node iterables
function processNodes<N>(nodeIterable: Graph.NodeWalker<N>): Array<number> {
return Array.from(Graph.indices(nodeIterable))
}
// Access node data using values() or entries()
const nodeData = Array.from(Graph.values(dfsNodes)) // ["A", "B"]
const nodeEntries = Array.from(Graph.entries(allNodes)) // [[0, "A"], [1, "B"]]

Mutations

Since v3.18.0

Adds a new edge to a mutable graph and returns its index.

Signature

declare function addEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, source: number, target: number, data: E): number

Example

import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const edge = Graph.addEdge(mutable, nodeA, nodeB, 42)
console.log(edge) // EdgeIndex with value 0
})
Since v3.18.0

Adds a new node to a mutable graph and returns its index.

Signature

declare function addNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, data: N): number

Example

import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
console.log(nodeA) // NodeIndex with value 0
console.log(nodeB) // NodeIndex with value 1
})

Creates a mutable scope for safe graph mutations by copying the data structure.

Signature

declare function beginMutation<N, E, T extends Kind = "directed">(graph: Graph<N, E, T>): MutableGraph<N, E, T>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>()
const mutable = Graph.beginMutation(graph)
// Now mutable can be safely modified without affecting original graph

Converts a mutable graph back to an immutable graph, ending the mutation scope.

Signature

declare function endMutation<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>): Graph<N, E, T>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>()
const mutable = Graph.beginMutation(graph)
// ... perform mutations on mutable ...
const newGraph = Graph.endMutation(mutable)
Since v3.18.0

Performs scoped mutations on a graph, automatically managing the mutation lifecycle.

Signature

declare const mutate: {
<N, E, T extends Kind = "directed">(f: (mutable: MutableGraph<N, E, T>) => void): (graph: Graph<N, E, T>) => Graph<N, E, T>;
<N, E, T extends Kind = "directed">(graph: Graph<N, E, T>, f: (mutable: MutableGraph<N, E, T>) => void): Graph<N, E, T>;
}

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>()
const newGraph = Graph.mutate(graph, (mutable) => {
// Safe mutations go here
// mutable gets automatically converted back to immutable
})

Removes an edge from a mutable graph.

Signature

declare function removeEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndex: number): void

Example

import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const edge = Graph.addEdge(mutable, nodeA, nodeB, 42)
// Remove the edge
Graph.removeEdge(mutable, edge)
})

Removes a node and all its incident edges from a mutable graph.

Signature

declare function removeNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, nodeIndex: number): void

Example

import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
Graph.addEdge(mutable, nodeA, nodeB, 42)
// Remove nodeA and all edges connected to it
Graph.removeNode(mutable, nodeA)
})

Updates a single edge's data by applying a transformation function.

Signature

declare function updateEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndex: number, f: (data: E) => E): void

Example

import { Graph } from "effect"
const result = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const edgeIndex = Graph.addEdge(mutable, nodeA, nodeB, 10)
Graph.updateEdge(mutable, edgeIndex, (data) => data * 2)
})
const edgeData = Graph.getEdge(result, 0)
console.log(edgeData) // Option.some({ source: 0, target: 1, data: 20 })

Queries

Get directed neighbors of a node in a specific direction.

Signature

declare function neighborsDirected<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number, direction: Direction): Array<number>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, "A->B")
})
const nodeA = 0
const nodeB = 1
// Get outgoing neighbors (nodes that nodeA points to)
const outgoing = Graph.neighborsDirected(graph, nodeA, "outgoing")
// Get incoming neighbors (nodes that point to nodeB)
const incoming = Graph.neighborsDirected(graph, nodeB, "incoming")

Returns the incoming neighbor node indices for a node in a directed graph.

Throws a GraphError when used with an undirected graph.

Signature

declare function predecessors<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>

Returns the outgoing neighbor node indices for a node in a directed graph.

Throws a GraphError when used with an undirected graph.

Signature

declare function successors<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>

Symbol

Since v3.18.0

Unique identifier for Graph instances.

Signature

declare const TypeId: "~effect/Graph"
Since v3.18.0

Type identifier for Graph instances.

Signature

type TypeId = typeof TypeId

Transformations

Filters edges by removing those that don't match the predicate. This function modifies the mutable graph in place.

Signature

declare function filterEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, predicate: (data: E) => boolean): void

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 5)
Graph.addEdge(mutable, b, c, 15)
Graph.addEdge(mutable, c, a, 25)
// Keep only edges with weight >= 10
Graph.filterEdges(mutable, (data) => data >= 10)
})
console.log(Graph.edgeCount(graph)) // 2 (edge with weight 5 removed)

Filters and optionally transforms edges in a mutable graph using a predicate function. Edges that return Option.none are removed from the graph.

Signature

declare function filterMapEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: E) => Option<E>): void

Example

import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 5)
Graph.addEdge(mutable, b, c, 15)
Graph.addEdge(mutable, c, a, 25)
// Keep only edges with weight >= 10 and double their weight
Graph.filterMapEdges(mutable, (data) =>
data >= 10 ? Option.some(data * 2) : Option.none()
)
})
console.log(Graph.edgeCount(graph)) // 2 (edges with weight 5 removed)

Filters and optionally transforms nodes in a mutable graph using a predicate function. Nodes that return Option.none are removed along with all their connected edges.

Signature

declare function filterMapNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: N) => Option<N>): void

Example

import { Graph, Option } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "active")
const b = Graph.addNode(mutable, "inactive")
const c = Graph.addNode(mutable, "active")
Graph.addEdge(mutable, a, b, 1)
Graph.addEdge(mutable, b, c, 2)
// Keep only "active" nodes and transform to uppercase
Graph.filterMapNodes(mutable, (data) =>
data === "active" ? Option.some(data.toUpperCase()) : Option.none()
)
})
console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain)

Filters nodes by removing those that don't match the predicate. This function modifies the mutable graph in place.

Signature

declare function filterNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, predicate: (data: N) => boolean): void

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
Graph.addNode(mutable, "active")
Graph.addNode(mutable, "inactive")
Graph.addNode(mutable, "pending")
Graph.addNode(mutable, "active")
// Keep only "active" nodes
Graph.filterNodes(mutable, (data) => data === "active")
})
console.log(Graph.nodeCount(graph)) // 2 (only "active" nodes remain)
Since v3.18.0

Transforms all edge data in a mutable graph using the provided mapping function.

Signature

declare function mapEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: E) => E): void

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 10)
Graph.addEdge(mutable, b, c, 20)
Graph.mapEdges(mutable, (data) => data * 2)
})
const edgeData = Graph.getEdge(graph, 0)
console.log(edgeData) // Option.some({ source: 0, target: 1, data: 20 })
Since v3.18.0

Creates a new graph with transformed node data using the provided mapping function.

Signature

declare function mapNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: N) => N): void

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
Graph.addNode(mutable, "node a")
Graph.addNode(mutable, "node b")
Graph.addNode(mutable, "node c")
Graph.mapNodes(mutable, (data) => data.toUpperCase())
})
const nodeData = Graph.getNode(graph, 0)
console.log(nodeData) // Option.some("NODE A")
Since v3.18.0

Reverses all edge directions in a mutable graph by swapping source and target nodes.

Signature

declare function reverse<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>): void

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
const c = Graph.addNode(mutable, "C")
Graph.addEdge(mutable, a, b, 1) // A -> B
Graph.addEdge(mutable, b, c, 2) // B -> C
Graph.reverse(mutable) // Now B -> A, C -> B
})
const edge0 = Graph.getEdge(graph, 0)
console.log(edge0) // Option.some({ source: 1, target: 0, data: 1 }) - B -> A

Updates a single node's data by applying a transformation function.

Signature

declare function updateNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, index: number, f: (data: N) => N): void

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
Graph.addNode(mutable, "Node A")
Graph.addNode(mutable, "Node B")
Graph.updateNode(mutable, 0, (data) => data.toUpperCase())
})
const nodeData = Graph.getNode(graph, 0)
console.log(nodeData) // Option.some("NODE A")

Utilities

Since v3.18.0

Returns an iterator over [index, data] entries in the walker.

Signature

declare function entries<T, N>(walker: Walker<T, N>): Iterable<[T, N]>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, 1)
})
const dfs = Graph.dfs(graph, { start: [0] })
const entries = Array.from(Graph.entries(dfs))
console.log(entries) // [[0, "A"], [1, "B"]]
Since v3.18.0

Returns an iterator over the indices in the walker.

Signature

declare function indices<T, N>(walker: Walker<T, N>): Iterable<T>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, 1)
})
const dfs = Graph.dfs(graph, { start: [0] })
const indices = Array.from(Graph.indices(dfs))
console.log(indices) // [0, 1]
Since v3.18.0

Returns an iterator over the values (data) in the walker.

Signature

declare function values<T, N>(walker: Walker<T, N>): Iterable<N>

Example

import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => {
const a = Graph.addNode(mutable, "A")
const b = Graph.addNode(mutable, "B")
Graph.addEdge(mutable, a, b, 1)
})
const dfs = Graph.dfs(graph, { start: [0] })
const values = Array.from(Graph.values(dfs))
console.log(values) // ["A", "B"]

Utils

Exports a graph to GraphViz DOT format for visualization.

Signature

declare function toGraphViz<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, options?: GraphVizOptions<N, E>): string

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const nodeA = Graph.addNode(mutable, "Node A")
const nodeB = Graph.addNode(mutable, "Node B")
const nodeC = Graph.addNode(mutable, "Node C")
Graph.addEdge(mutable, nodeA, nodeB, 1)
Graph.addEdge(mutable, nodeB, nodeC, 2)
Graph.addEdge(mutable, nodeC, nodeA, 3)
})
const dot = Graph.toGraphViz(graph)
console.log(dot)
// digraph G {
// "0" [label="Node A"];
// "1" [label="Node B"];
// "2" [label="Node C"];
// "0" -> "1" [label="1"];
// "1" -> "2" [label="2"];
// "2" -> "0" [label="3"];
// }
Since v3.18.0

Exports a graph to Mermaid diagram format for visualization.

Signature

declare function toMermaid<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, options?: MermaidOptions<N, E>): string

Example

import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
const app = Graph.addNode(mutable, "App")
const db = Graph.addNode(mutable, "Database")
const cache = Graph.addNode(mutable, "Cache")
Graph.addEdge(mutable, app, db, 1)
Graph.addEdge(mutable, app, cache, 2)
})
const mermaid = Graph.toMermaid(graph)
console.log(mermaid)
// flowchart TD
// 0["App"]
// 1["Database"]
// 2["Cache"]
// 0 -->|"1"| 1
// 0 -->|"2"| 2