Graph
Models relationships between indexed nodes and edges.
This module provides immutable and scoped-mutable graph data structures. A graph can be directed or undirected, and it can store user-defined data on both nodes and edges. The module includes traversal, analysis, path-finding, transformation, and diagram export utilities.
Algorithms
allShortestPaths
Lazily enumerates all simple paths tied for minimum total cost.
When to use
Use when every distinct route tied for the minimum total cost is required.
Details
Parallel edges produce distinct paths. Edge costs must be non-negative;
Infinity behaves as unavailable.
Gotchas
The number of tied paths can still be large. Missing endpoints, invalid
costs, arithmetic overflow, or an invalid limit throw a GraphError.
Mutable graphs are snapshotted when iteration begins.
See
- dijkstra when one shortest path is sufficient
- simplePaths for routes regardless of cost
Signature
declare const allShortestPaths: { <E>(config: AllShortestPathsConfig<E>): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => PathWalker<E>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: AllShortestPathsConfig<E>): PathWalker<E>;}articulationPoints
Returns the nodes whose removal increases the number of connected components.
When to use
Use when locating single-node failure points in an undirected network.
Details
Disconnected components, parallel edges, and self-loops are handled by an
iterative, stack-safe low-link traversal in O(V + E) time. Results follow
graph node order.
Gotchas
Throws a GraphError when used with a directed graph.
See
- bridges for single-edge failure points
- biconnectedComponents for the regions joined at articulation points
Signature
declare function articulationPoints<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<number>Example
(Finding articulation points)
import { Graph } from "effect"
const graph = Graph.undirected<void, void>((mutable) => { for (let i = 0; i < 3; i++) Graph.addNode(mutable, undefined) Graph.addEdge(mutable, 0, 1, undefined) Graph.addEdge(mutable, 1, 2, undefined)})
Graph.articulationPoints(graph) // => [1]Finds the shortest path from the configured source node to the target node using the A* pathfinding algorithm.
When to use
Use when a meaningful heuristic can reduce point-to-point search compared with Dijkstra's algorithm.
Details
The edge-cost function must return non-negative weights and not NaN.
Infinity is allowed and behaves like an impassable edge. Returns
Option.none() when the target is not reachable.
Gotchas
The heuristic must be consistent for the shortest-path guarantee and must
return finite values. Missing endpoints, invalid edge costs, or non-finite
heuristic values or arithmetic results throw a GraphError.
See
- dijkstra when no useful heuristic is available
- bellmanFord when edge costs may be negative
Signature
declare const astar: { <E, N>(config: AstarConfig<E, N>): <T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<PathResult<E>>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: AstarConfig<E, N>): Option<PathResult<E>>;}Example
(Finding shortest paths with A*)
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 heuristicconst 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})
Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([2, [0, 1, 2]])bellmanFord
Finds the shortest path from the configured source node to the target node using the Bellman-Ford algorithm.
When to use
Use when one source-to-target shortest-path query may traverse negative-cost edges.
Details
Negative edge weights are allowed, and Infinity behaves like an impassable
edge. Returns Option.none() when the target is unreachable. A reachable
negative cycle only causes failure when it can affect the target.
Gotchas
Missing endpoints, unsupported weights, finite-range overflow, or a relevant
negative cycle throw a GraphError. In an undirected graph, any reachable
negative edge forms a negative cycle because it can be traversed both ways.
See
- dijkstra for non-negative edge costs
Signature
declare const bellmanFord: { <E>(config: BellmanFordConfig<E>): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<PathResult<E>>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: BellmanFordConfig<E>): Option<PathResult<E>>;}Example
(Finding shortest paths with Bellman-Ford)
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})
Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([2, [0, 1, 2]])biconnectedComponents
Returns the maximal biconnected node components of an undirected graph.
When to use
Use when decomposing an undirected graph into maximal regions that remain connected after removing any one node from the region.
Details
Articulation points can occur in more than one component. Isolated vertices
are excluded, while a vertex with a self-loop forms a singleton component.
Nodes within components and the components themselves follow graph order.
Parallel edges are treated independently. The iterative low-link traversal
is stack-safe and runs in O(V + E) time.
Gotchas
Throws a GraphError when used with a directed graph.
See
- articulationPoints for the nodes shared between components
- bridges for edges whose removal disconnects the graph
Signature
declare function biconnectedComponents<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<Array<number>>Example
(Finding biconnected components)
import { Graph } from "effect"
const graph = Graph.undirected<void, void>((mutable) => { for (let i = 0; i < 5; i++) Graph.addNode(mutable, undefined) Graph.addEdge(mutable, 0, 1, undefined) Graph.addEdge(mutable, 1, 2, undefined) Graph.addEdge(mutable, 2, 0, undefined) Graph.addEdge(mutable, 2, 3, undefined) Graph.addEdge(mutable, 3, 4, undefined) Graph.addEdge(mutable, 4, 2, undefined)})
Graph.biconnectedComponents(graph) // => [[0, 1, 2], [2, 3, 4]]Returns the edges whose removal increases the number of connected components.
When to use
Use when locating single-edge failure points in an undirected network.
Details
Parent edges are tracked by edge index, so a parallel edge prevents either
edge from being a bridge. Self-loops are never bridges. Results follow graph
edge order. The iterative low-link traversal is stack-safe and runs in
O(V + E) time.
Gotchas
Throws a GraphError when used with a directed graph.
See
- articulationPoints for single-node failure points
- biconnectedComponents for maximal regions without an articulation split
Signature
declare function bridges<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<number>Example
(Finding bridge edges)
import { Graph } from "effect"
const graph = Graph.undirected<void, void>((mutable) => { for (let i = 0; i < 3; i++) Graph.addNode(mutable, undefined) Graph.addEdge(mutable, 0, 1, undefined) Graph.addEdge(mutable, 1, 2, undefined)})
Graph.bridges(graph) // => [0, 1]connectedComponents
Returns the connected components of an undirected graph.
When to use
Use when partitioning an undirected graph into groups connected by paths.
Details
Each component is represented as an array of node indices. Isolated nodes form singleton components.
Gotchas
Throws a GraphError when used with a directed graph.
See
- isConnected when only a boolean connectivity check is needed
- weaklyConnectedComponents for directed graphs with orientation ignored
- stronglyConnectedComponents for mutual directed reachability
Signature
declare function connectedComponents<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<Array<number>>Example
(Finding connected components)
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})
Graph.connectedComponents(graph) // => [[0, 1], [2, 3]]Finds the shortest path from the configured source node to the target node using Dijkstra's algorithm.
When to use
Use when you need one source-to-target shortest path and every edge cost is non-negative.
Details
Edge costs must be non-negative and not NaN. Infinity is allowed and
behaves like an impassable edge. Returns Option.none() when the target is
not reachable.
Gotchas
Throws a GraphError when either endpoint is missing or an edge cost is
negative or NaN, or when a path distance exceeds the finite number range.
See
- astar when a useful heuristic can guide the search
- bellmanFord when edge costs may be negative
- floydWarshall when shortest paths are needed for all pairs
Signature
declare const dijkstra: { <E>(config: DijkstraConfig<E>): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<PathResult<E>>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: DijkstraConfig<E>): Option<PathResult<E>>;}Example
(Finding shortest paths with Dijkstra)
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})
Option.map(result, ({ distance, path }) => [distance, path] as const) // => Option.some([7, [0, 1, 2]])Returns one cycle in a graph, if present.
When to use
Use when you need the nodes and edges of a concrete cycle for diagnostics or reporting.
Details
Directed cycles respect edge orientation. A self-loop is represented as a one-edge cycle, and two parallel undirected edges form a two-edge cycle.
See
- isAcyclic when only a boolean cycle check is needed
Signature
declare function findCycle<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): Option<CycleResult>floydWarshall
Finds shortest paths between all pairs of nodes using the Floyd-Warshall algorithm.
When to use
Use when many or all node pairs will be queried and cubic computation plus quadratic result storage is acceptable.
Details
Computes distances, reconstructed node paths, and edge-data paths for every
source and target pair in O(V^3) time. Negative edge weights are allowed, and
Infinity behaves like an impassable edge.
Gotchas
A GraphError is thrown if any edge weight is NaN or -Infinity, or if
finite arithmetic overflows or underflows, or if any negative cycle is
detected.
See
- dijkstra for one query with non-negative edge costs
- bellmanFord for one query that may include negative edge costs
Signature
declare const floydWarshall: { <E>(cost: (edgeData: E) => number): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => AllPairsResult<E>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, cost: (edgeData: E) => number): AllPairsResult<E>;}Example
(Finding all-pairs shortest paths)
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 shortest = { distance: result.distances.get(0)?.get(2), path: result.paths.get(0)?.get(2) }shortest // => { distance: 5, path: [0, 1, 2] }Checks whether the graph is acyclic (contains no cycles).
When to use
Use when validating that a graph contains no cycle and a cycle witness is not needed.
Details
Directed cycles respect edge orientation. Self-loops are cycles, and two parallel edges form a cycle in an undirected graph.
See
Signature
declare function isAcyclic<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): booleanExample
(Checking cycles)
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")})Graph.isAcyclic(dag) // => true
// Cyclic directed graphconst 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})Graph.isAcyclic(cyclic) // => falseisBipartite
Checks whether an undirected graph is bipartite.
When to use
Use when validating that nodes can be divided into two groups with every edge crossing between the groups.
Details
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.
See
- maximumBipartiteMatching for matching nodes after validation
Signature
declare function isBipartite<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): booleanExample
(Checking bipartite graphs)
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")})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)})Graph.isBipartite(triangle) // => falsemaximumBipartiteMatching
Returns a maximum-cardinality matching of an undirected bipartite graph.
When to use
Use when assigning as many disjoint pairs as possible between the two sides of a bipartite graph, such as workers to jobs or users to resources.
Details
The bipartition is derived internally. Self-loops and odd cycles throw a
GraphError. Isolated nodes are allowed. Parallel edges do not change the
matching cardinality, and the first edge in graph order between each matched
pair is reported. Results follow left-partition graph order. Hopcroft-Karp
runs in O(E * sqrt(V)) time.
Gotchas
The graph must be undirected and bipartite. The derived left and right sides are not based on stored edge orientation.
See
- isBipartite for validating the graph without computing a matching
Signature
declare function maximumBipartiteMatching<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): Array<BipartiteMatch>Example
(Matching a bipartite graph)
import { Graph } from "effect"
const graph = Graph.undirected<string, string>((mutable) => { for (const node of ["A", "B", "X", "Y"]) Graph.addNode(mutable, node) Graph.addEdge(mutable, 0, 2, "A-X") Graph.addEdge(mutable, 0, 3, "A-Y") Graph.addEdge(mutable, 1, 2, "B-X")})
Graph.maximumBipartiteMatching(graph) // => [{ left: 0, right: 3, edge: 1 }, { left: 1, right: 2, edge: 2 }]maximumFlow
Returns a maximum flow and corresponding minimum cut for a directed graph.
When to use
Use when computing the greatest transferable capacity from one node to another and per-edge flow values are required.
Details
Parallel edges retain independent capacities, self-loops carry no
source-to-target flow, and the flow map includes every original edge in graph
order, including zero-flow edges. Edmonds-Karp runs in O(V * E^2) time.
Gotchas
The graph must be directed. Capacities must be finite and non-negative.
Missing or equal endpoints, invalid capacities, and a total flow outside the
finite number range throw a GraphError. Self-loops always carry zero flow.
See
- minimumCut for the residual-reachability partition
Signature
declare const maximumFlow: { <E>(config: MaximumFlowConfig<E>): <N>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => MaximumFlowResult; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, config: MaximumFlowConfig<E>): MaximumFlowResult;}Example
(Computing maximum flow)
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { for (const node of ["source", "a", "target"]) Graph.addNode(mutable, node) Graph.addEdge(mutable, 0, 1, 3) Graph.addEdge(mutable, 1, 2, 2) Graph.addEdge(mutable, 0, 2, 1)})
Graph.maximumFlow(graph, { source: 0, target: 2, capacity: (edge) => edge }).value // => 3minimumCut
Returns a minimum cut and its node partitions for a directed graph.
When to use
Use when identifying the minimum-capacity edges that separate a source from a target, together with the resulting node partitions.
Details
The source partition contains nodes reachable from the source in the final
residual network; the target partition contains its complement. Both follow
graph node order. Cut edges follow graph edge order, and their total capacity
equals the returned maximum-flow value. Validation, parallel-edge,
self-loop, and O(V * E^2) complexity behavior match maximumFlow.
Gotchas
The graph must be directed. Invalid capacities, missing endpoints, or equal
source and target nodes throw a GraphError.
See
- maximumFlow for per-edge flow values
Signature
declare const minimumCut: { <E>(config: MaximumFlowConfig<E>): <N>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => MinimumCutResult; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, config: MaximumFlowConfig<E>): MinimumCutResult;}Example
(Partitioning a minimum cut)
import { Graph } from "effect"
const graph = Graph.directed<string, number>((mutable) => { for (const node of ["source", "a", "target"]) Graph.addNode(mutable, node) Graph.addEdge(mutable, 0, 1, 2) Graph.addEdge(mutable, 1, 2, 1)})
Graph.minimumCut(graph, { source: 0, target: 2, capacity: (edge) => edge }).source // => [0, 1]minimumSpanningForest
Returns a minimum spanning forest of an undirected graph using Kruskal's algorithm.
When to use
Use when selecting a minimum-cost acyclic connector for every connected component of an undirected graph.
Details
All node indices and selected edge indices are preserved. Negative finite
weights are allowed, Infinity marks an unavailable edge, and equal weights
are resolved by original edge order. Disconnected inputs produce a forest,
and isolated nodes remain present.
Gotchas
Throws a GraphError for a directed graph or when a weight is NaN or
-Infinity. Edges weighted Infinity are omitted.
Signature
declare const minimumSpanningForest: { <E>(cost: (edgeData: E) => number): <N>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">) => Graph<N, E, "undirected">; <N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">, cost: (edgeData: E) => number): Graph<N, E, "undirected">;}simplePaths
Lazily enumerates simple source-to-target paths in depth-first edge order.
When to use
Use when you need possible loop-free routes rather than only an optimal route.
Details
Nodes are never repeated within a path, so enumeration is finite even for cyclic graphs. Path distance is the number of traversed edges.
Gotchas
The number of simple paths can be exponential. Missing endpoints or an
invalid limit throw a GraphError. Mutable graphs are snapshotted when
iteration begins.
See
- allShortestPaths for enumerating only minimum-cost routes
Signature
declare const simplePaths: { <E>(config: SimplePathsConfig): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => PathWalker<E>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config: SimplePathsConfig): PathWalker<E>;}stronglyConnectedComponents
Returns the strongly connected components of a directed graph.
When to use
Use when grouping nodes so every node in a component can reach every other node in that component.
Details
Each component is represented as an array of node indices and is computed with Kosaraju's algorithm.
Gotchas
Throws a GraphError when used with an undirected graph.
See
- isStronglyConnected when only a boolean check is needed
- weaklyConnectedComponents when edge orientation should be ignored
Signature
declare function stronglyConnectedComponents<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): Array<Array<number>>Example
(Finding strongly connected components)
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})
Graph.stronglyConnectedComponents(graph) // => [[0, 2, 1]]transitiveReduction
Returns the transitive reduction of a directed acyclic graph.
When to use
Use when simplifying a dependency DAG while preserving which nodes can reach which other nodes.
Details
The result preserves reachability with the fewest structural source-target pairs. Node and retained edge indices are preserved.
Gotchas
This operation is structural and ignores edge costs. Parallel edges are
coalesced by retaining the first edge for each required pair. Throws a
GraphError for an undirected graph or cyclic input.
Signature
declare function transitiveReduction<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): Graph<N, E, "directed">unweightedDistances
Returns minimum unweighted distances from a source to every reachable node.
When to use
Use when every edge represents one step and you need hop counts from one source.
Details
Directed traversal is outgoing by default and can be changed with
direction.
Gotchas
Throws a GraphError when the source does not exist.
See
Signature
declare const unweightedDistances: { (source: number, options?: ReachabilityConfig): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Map<number, number>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, source: number, options?: ReachabilityConfig): Map<number, number>;}weaklyConnectedComponents
Finds weakly connected components in a directed graph.
When to use
Use when grouping directed nodes by connectivity while ignoring edge orientation.
Details
Edge direction is ignored while partitioning nodes. Isolated nodes form singleton components.
Gotchas
Throws a GraphError when used with an undirected graph.
See
- isWeaklyConnected when only a boolean check is needed
- stronglyConnectedComponents for mutual directed reachability
Signature
declare function weaklyConnectedComponents<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): Array<Array<number>>Configuration
AllShortestPathsConfig interface
Configuration for enumerating all tied shortest paths.
When to use
Use when bounding enumeration of every route tied for minimum total cost.
Details
Edge costs must be non-negative. limit bounds the number of yielded paths
and defaults to Infinity.
Gotchas
Invalid costs and limits throw a GraphError when evaluated.
Signature
interface AllShortestPathsConfig<E> extends DijkstraConfig<E> { readonly limit?: number;}AstarConfig interface
Configuration for finding a shortest path with the A* algorithm.
When to use
Use when configuring astar for point-to-point shortest-path searches where
node data can provide a heuristic estimate toward the target.
Details
Specifies the source and target node indices, an edge-cost function that maps edge data to non-negative weights, and a heuristic that estimates the remaining cost from a node to the target.
Gotchas
Heuristic values must be finite and the heuristic must be consistent for A* to guarantee a shortest path.
Signature
interface AstarConfig<E, N> { cost: (edgeData: E) => number; heuristic: (sourceNodeData: N, targetNodeData: N) => number; source: number; target: number;}BellmanFordConfig interface
Configuration for finding a shortest path with the Bellman-Ford algorithm.
When to use
Use when configuring bellmanFord to find a shortest path where edge
weights may be negative.
Details
Specifies the source and target node indices, plus a cost function that maps each edge's data to a numeric weight.
Signature
interface BellmanFordConfig<E> { cost: (edgeData: E) => number; source: number; target: number;}DijkstraConfig interface
Configuration for finding a shortest path with Dijkstra's algorithm.
When to use
Use when configuring dijkstra to find a shortest path between two existing
node indices with non-negative edge costs.
Details
Specifies the source and target node indices, plus a cost function that maps
each edge's data to a non-negative numeric weight. Infinity is allowed and
behaves like an impassable edge.
Gotchas
dijkstra throws a GraphError when either endpoint does not exist or when
the cost function returns a negative weight or NaN.
Signature
interface DijkstraConfig<E> { cost: (edgeData: E) => number; source: number; target: number;}ExternalsConfig interface
Configuration for selecting external nodes.
When to use
Use to configure how externals identifies graph boundary nodes when you
need sinks with no outgoing edges or sources with no incoming edges.
Details
direction chooses which missing edge direction makes a node external:
"outgoing" selects nodes with no outgoing edges, and "incoming" selects
nodes with no incoming edges. If omitted, direction defaults to
"outgoing".
Signature
interface ExternalsConfig { readonly direction?: Direction;}GraphVizOptions interface
Configuration options for GraphViz DOT format generation from graphs.
When to use
Use when customizing labels or the graph name produced by toGraphViz.
Details
These options customize node labels, edge labels, and graph naming in DOT format compatible with GraphViz tools.
See
- toGraphViz for generating DOT output
Signature
interface GraphVizOptions<N, E> { readonly edgeLabel?: (data: E) => string; readonly graphName?: string; readonly nodeLabel?: (data: N) => string;}Example
(Configuring GraphViz labels)
import type { Graph } from "effect"
// Basic options with custom labelsconst basicOptions: Graph.GraphVizOptions<string, number> = { nodeLabel: (data) => `Node: ${data}`, edgeLabel: (data) => `Weight: ${data}`}
// Complete options with graph namingconst namedOptions: Graph.GraphVizOptions<string, string> = { nodeLabel: (data) => data.toUpperCase(), edgeLabel: (data) => data, graphName: "MyDependencyGraph"}Array.of(basicOptions.nodeLabel?.("A"), namedOptions.graphName) // => ["Node: A", "MyDependencyGraph"]IdentityOptions interface
Configures node and edge identity for graph set operations.
When to use
Use when logical graph membership should be based on a stable key rather than the complete node or edge payload.
Details
Both functions default to using the complete node or edge data. Edge identity also includes the identities of its endpoint nodes and the graph kind. Projected identities use Effect equality and hashing semantics.
Gotchas
Edge identity defines set membership, not edge multiplicity. Parallel edges with the same endpoint identities and projected edge identity are treated as the same member by graph set operations.
Signature
interface IdentityOptions<N, E, NI = N, EI = E> { readonly edgeIdentity?: (edge: E) => EI; readonly nodeIdentity?: (node: N) => NI;}MaximumFlowConfig interface
Configuration for source-to-target flow algorithms.
When to use
Use when defining endpoints and edge capacities for maximumFlow or
minimumCut.
Details
capacity receives stored edge data and must return a finite,
non-negative number.
Gotchas
The source and target must be distinct existing nodes in a directed graph.
Signature
interface MaximumFlowConfig<E> { readonly capacity: (edge: E) => number; readonly source: number; readonly target: number;}MermaidOptions interface
Configuration options for Mermaid diagram generation from graphs.
When to use
Use when customizing labels, layout, node shapes, or syntax emitted by
toMermaid.
Details
These options customize node labels, edge labels, diagram type, layout direction and node shapes in Mermaid format.
See
- toMermaid for generating Mermaid output
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;}Example
(Configuring Mermaid output)
import type { Graph } from "effect"
// Basic options with custom labelsconst basicOptions: Graph.MermaidOptions<string, number> = { nodeLabel: (data) => `Node: ${data}`, edgeLabel: (data) => `Weight: ${data}`}
// Advanced options with all featuresconst advancedOptions: Graph.MermaidOptions<string, string> = { nodeLabel: (data) => data.toUpperCase(), edgeLabel: (data) => data, diagramType: "flowchart", direction: "LR", nodeShape: (data) => data.includes("start") ? "circle" : "rectangle"}Array.of(basicOptions.nodeLabel?.("A"), advancedOptions.nodeShape?.("start")) // => ["Node: A", "circle"]NeighborhoodConfig interface
Configuration for selecting a graph neighborhood.
Details
radius limits the edge distance from the center node and defaults to 1.
It accepts non-negative integers and Infinity.
direction controls how directed edges are traversed and defaults to
"outgoing".
Signature
interface NeighborhoodConfig { readonly direction?: TraversalDirection; readonly radius?: number;}ReachabilityConfig interface
Configuration for unweighted reachability queries.
When to use
Use when controlling whether reachability follows outgoing edges, incoming edges, or either direction.
Details
direction defaults to "outgoing" and is ignored for undirected graphs.
Signature
interface ReachabilityConfig { readonly direction?: TraversalDirection;}SearchConfig interface
Configuration for DFS, BFS, and postorder graph traversals.
When to use
Use to configure the starting node indices and edge-following direction for lazy graph traversals.
Details
start supplies the node indices where traversal begins. If it is omitted,
the iterator is empty. Distinct starts are prioritized in supplied order and
duplicates are ignored. direction chooses whether traversal follows outgoing
edges, incoming edges, or ignores edge direction. radius limits traversal
by edge distance from the nearest start node and accepts non-negative integers
or Infinity; omitting it means unbounded traversal.
Gotchas
Traversal creation validates and copies start, and throws a GraphError
when a start node does not exist or radius is invalid. Each fresh iterator
revalidates those starts against the graph snapshot it captures. Later
mutations are not observed by an active iterator.
Signature
interface SearchConfig { readonly direction?: TraversalDirection; readonly radius?: number; readonly start?: Array<number>;}SimplePathsConfig interface
Configuration for lazy simple-path enumeration.
When to use
Use when bounding enumeration of loop-free routes between two nodes.
Details
limit bounds the number of yielded paths and defaults to Infinity.
Gotchas
limit must be a non-negative integer or Infinity.
Signature
interface SimplePathsConfig { readonly limit?: number; readonly source: number; readonly target: number;}TopoConfig interface
Configuration for the topological sort iterator.
When to use
Use to prioritize specific zero in-degree nodes in a topological sort.
Details
initials optionally supplies zero in-degree node indices used as
prioritized initial queue entries. Topological sorting still includes the
other zero in-degree nodes and produces a complete topological order.
Gotchas
Throws a GraphError when any initial node has incoming edges.
Signature
interface TopoConfig { readonly initials?: Array<number>;}Constructors
Creates a directed graph, optionally with initial mutations.
When to use
Use when relationships have a source-to-target direction, such as dependencies, workflows, or routing links.
Gotchas
The mutable callback handle is finalized when the callback returns and must not be retained for later mutation.
Signature
declare const directed: <N, E>(mutate?: (mutable: MutableDirectedGraph<N, E>) => undefined) => DirectedGraph<N, E>Example
(Creating a directed graph)
import { Graph } from "effect"
// Directed graph with initial nodes and edgesconst 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")})Array.of(Graph.nodeCount(graph), Graph.edgeCount(graph)) // => [3, 2]fromSnapshot
Reconstructs an immutable graph from its indexed active structure.
When to use
Use when importing a snapshot or other externally indexed graph structure.
Prefer directed or undirected when creating a new graph without existing
identifiers.
Gotchas
The node and edge arrays must be ordered by strictly increasing,
non-negative safe integer indexes, and every edge endpoint must reference a
node in the snapshot. Invalid snapshots throw a GraphError. Historical
removed identifiers after the greatest active index are not retained.
See
- toSnapshot for capturing a graph snapshot
Signature
declare function fromSnapshot<N, E, T extends Kind>(snapshot: Snapshot<N, E, T>): Graph<N, E, T>Example
(Preserving graph indexes)
import { Graph } from "effect"
const graph = Graph.fromSnapshot({ type: "directed", nodes: [{ index: 2, data: "A" }, { index: 5, data: "B" }], edges: [{ index: 3, source: 2, target: 5, data: 1 }]})
Graph.toSnapshot(graph).edges[0].index // => 3Creates a graph constructor for the specified graph kind.
When to use
Use when the graph kind is selected dynamically. Prefer directed or
undirected when the kind is known statically.
See
- directed for constructing a directed graph directly
- undirected for constructing an undirected graph directly
Signature
declare function make<T extends Kind>(type: T): <N, E>(mutate?: (mutable: MutableGraph<N, E, T>) => undefined) => Graph<N, E, T>Example
(Constructing by kind)
import { Graph } from "effect"
const makeGraph = Graph.make("directed")const graph = makeGraph<string, number>((mutable) => { Graph.addNode(mutable, "A")})
graph.type // => "directed"undirected
Creates an undirected graph, optionally with initial mutations.
When to use
Use when relationships connect both endpoints symmetrically, such as social connections or physical links.
Gotchas
The mutable callback handle is finalized when the callback returns and must not be retained for later mutation.
Signature
declare const undirected: <N, E>(mutate?: (mutable: MutableUndirectedGraph<N, E>) => undefined) => UndirectedGraph<N, E>Example
(Creating an undirected graph)
import { Graph } from "effect"
// Undirected graph with initial nodes and edgesconst 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")})Array.of(Graph.nodeCount(graph), Graph.edgeCount(graph)) // => [3, 2]Converting
toGraphViz
Exports a graph to GraphViz DOT format for visualization.
When to use
Use when sending graph structure to GraphViz-compatible visualization or documentation tools.
See
- toMermaid for Mermaid diagram output
Signature
declare const toGraphViz: { <N, E>(options?: GraphVizOptions<N, E>): <T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => string; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, options?: GraphVizOptions<N, E>): string;}Example
(Exporting GraphViz DOT)
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)})
Graph.toGraphViz(graph).split("\n") // => ['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"];', "}"]Exports a graph to Mermaid diagram format for visualization.
When to use
Use when embedding graph diagrams in Markdown, documentation sites, or other Mermaid-compatible tools.
Details
Directed graphs default to flowchart with arrow edges, while undirected
graphs default to graph with line edges. Labels and node shapes can be
customized with MermaidOptions.
See
- toGraphViz for GraphViz DOT output
Signature
declare const toMermaid: { <N, E>(options?: MermaidOptions<N, E>): <T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => string; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, options?: MermaidOptions<N, E>): string;}Example
(Exporting a Mermaid diagram)
import { Graph } from "effect"
const graph = Graph.directed<string, string>((mutable) => { const app = Graph.addNode(mutable, "App") const database = Graph.addNode(mutable, "Database") Graph.addEdge(mutable, app, database, "queries")})
Graph.toMermaid(graph).split("\n") // => ["flowchart TD", ' 0["App"]', ' 1["Database"]', ' 0 -->|"queries"| 1']toSnapshot
Returns the active indexed structure of a graph.
When to use
Use when serializing a graph or passing its active structure across a boundary where node and edge identifiers must be preserved.
Details
Nodes and edges are returned in graph order with their current indexes.
Undirected edges retain their stored endpoint orientation, and each returned
node and edge record is newly allocated. The operation runs in O(V + E).
Gotchas
Node and edge payloads are not cloned. The snapshot also omits allocator history for identifiers that are no longer active.
See
- fromSnapshot for reconstructing an immutable graph
Signature
declare function toSnapshot<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): Snapshot<N, E, T>Example
(Round-tripping a graph snapshot)
import { Equal, Graph } from "effect"
const graph = Graph.fromSnapshot({ type: "undirected", nodes: [{ index: 2, data: "A" }, { index: 5, data: "B" }], edges: [{ index: 3, source: 5, target: 2, data: "A-B" }]})
Equal.equals(Graph.fromSnapshot(Graph.toSnapshot(graph)), graph) // => trueErrors
GraphError
Error thrown by graph operations when the requested graph structure is invalid, such as referencing a missing node or using unsupported edge weights.
When to use
Use when handling failures thrown by graph operations that reject invalid graph structure or unsupported algorithm inputs.
Signature
declare class GraphError extends YieldableError<this> & { readonly _tag: "GraphError";} & Readonly<{ readonly message: string;}> { constructor(args: { readonly message: string; });}Filtering
filterEdges
Filters edges by removing those that don't match the predicate. This function modifies the mutable graph in place. Nodes are retained even when removing edges leaves them isolated.
Gotchas
The predicate may query the graph, but cannot mutate or finalize the same graph while it runs.
Signature
declare function filterEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, predicate: (data: E) => boolean): voidExample
(Filtering edges)
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)})
Graph.edgeCount(graph) // => 2filterMapEdges
Filters and optionally transforms edges in a mutable graph using a predicate function. Edges that return Option.none are removed from the graph.
Gotchas
The function may query the graph, but cannot mutate or finalize the same graph while it runs. Retained payloads must remain the same edge type.
Signature
declare function filterMapEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: E) => Option<E>): voidExample
(Filtering and mapping edges)
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() )})
Graph.edgeCount(graph) // => 2filterMapNodes
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.
Gotchas
The function may query the graph, but cannot mutate or finalize the same graph while it runs. Retained payloads must remain the same node type.
Signature
declare function filterMapNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: N) => Option<N>): voidExample
(Filtering and mapping nodes)
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() )})
Graph.nodeCount(graph) // => 2filterNodes
Filters nodes by removing those that don't match the predicate. This function modifies the mutable graph in place. Removed nodes also remove all incident edges; retained node identifiers are preserved.
Gotchas
The predicate may query the graph, but cannot mutate or finalize the same graph while it runs.
Signature
declare function filterNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, predicate: (data: N) => boolean): voidExample
(Filtering nodes)
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")})
Graph.nodeCount(graph) // => 2Getters
Returns the degree of a node in an undirected graph.
Parallel edges count separately and a self-loop contributes two. Throws a
GraphError for a directed graph or missing node.
Signature
declare const degree: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">) => number; <N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">, nodeIndex: number): number;}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>): numberExample
(Counting edges)
import { Graph } from "effect"
const emptyGraph = Graph.directed<string, number>()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)})
Graph.edgeCount(graphWithEdges) // => 3edgesBetween
Returns all edge indices connecting the supplied nodes.
Directed graphs only include edges from source to target; undirected
graphs include either stored orientation. Parallel edges are retained.
Throws a GraphError when either node does not exist.
Signature
declare const edgesBetween: { (source: number, target: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Array<number>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, source: number, target: number): Array<number>;}Finds the first edge that matches the given predicate.
Signature
declare const findEdge: { <E>(predicate: (data: E, source: number, target: number) => boolean): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<number>; <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
(Finding the first matching edge)
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)})
Graph.findEdge(graph, (data) => data > 15) // => Option.some(1)Graph.findEdge(graph, (data) => data > 100) // => Option.none()Finds all edges that match the given predicate.
Signature
declare const findEdges: { <E>(predicate: (data: E, source: number, target: number) => boolean): <N, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Array<number>; <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
(Finding matching edges)
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)})
Graph.findEdges(graph, (data) => data >= 20) // => [1, 2]Graph.findEdges(graph, (data) => data > 100) // => []Finds the first node that matches the given predicate.
Signature
declare const findNode: { <N>(predicate: (data: N) => boolean): <E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<number>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: N) => boolean): Option<number>;}Example
(Finding the first matching node)
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")})
Graph.findNode(graph, (data) => data.startsWith("Node B")) // => Option.some(1)Graph.findNode(graph, (data) => data === "Node D") // => Option.none()Finds all nodes that match the given predicate.
Signature
declare const findNodes: { <N>(predicate: (data: N) => boolean): <E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Array<number>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, predicate: (data: N) => boolean): Array<number>;}Example
(Finding matching nodes)
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")})
Graph.findNodes(graph, (data) => data.startsWith("Start")) // => [0, 2]Graph.findNodes(graph, (data) => data === "Not Found") // => []Gets the edge data associated with an edge index safely, if it exists.
Signature
declare const getEdge: { (edgeIndex: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<Edge<E>>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, edgeIndex: number): Option<Edge<E>>;}Example
(Getting edge data)
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)})
Graph.getEdge(graph, 0) // => Option.some({ source: 0, target: 1, data: 42 })Gets the data associated with a node index safely, if it exists.
Signature
declare const getNode: { (nodeIndex: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Option<N>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Option<N>;}Example
(Getting node data)
import { Graph, Option } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Node A")})
Graph.getNode(graph, 0) // => Option.some("Node A")incidentEdges
Returns the indices of all edges incident to a node.
Each edge is returned once in graph edge order, including self-loops.
Throws a GraphError when the node does not exist.
Signature
declare const incidentEdges: { (nodeIndex: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Array<number>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Array<number>;}incomingEdges
Returns the indices of incoming edges for a node in a directed graph.
Parallel edges and self-loops are returned separately in reverse-adjacency
order. Throws a GraphError for an undirected graph or missing node.
Signature
declare const incomingEdges: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => Array<number>; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>;}Returns the in-degree of a node in a directed graph.
Parallel edges count separately and a self-loop contributes one. Throws a
GraphError for an undirected graph or missing node.
Signature
declare const inDegree: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => number; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): number;}Returns the neighboring node indices for a node.
Details
For directed graphs, neighbors are the targets of outgoing edges. For undirected graphs, neighbors are the other endpoints of incident edges. Each neighbor appears once in first edge occurrence order, including the queried node when it has a self-loop.
Gotchas
Returns an empty array when the node does not exist. For directed graphs,
use predecessors when incoming neighbors are required.
Signature
declare const neighbors: { (nodeIndex: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => Array<number>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): Array<number>;}Example
(Getting outgoing neighbors)
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)})
Graph.neighbors(graph, 0) // => [1, 2]Graph.neighbors(graph, 1) // => []neighborsDirected
Gets directed neighbors of a node in a specific direction.
When to use
Use when maintaining existing code that already passes an explicit traversal
direction. New code should prefer successors or predecessors.
Results contain each node once in first edge occurrence order, and a self-loop
contributes the queried node once.
Gotchas
Throws a GraphError when used with an undirected graph.
See
- successors for outgoing neighbors in a directed graph
- predecessors for incoming neighbors in a directed graph
Signature
declare const neighborsDirected: { (nodeIndex: number, direction: Direction): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => Array<number>; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number, direction: Direction): Array<number>;}Example
(Traversing directed neighbors)
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 = 0const 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")Array.of(outgoing, incoming) // => [[1], [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>): numberExample
(Counting nodes)
import { Graph } from "effect"
const emptyGraph = Graph.directed<string, number>()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")})
Graph.nodeCount(graphWithNodes) // => 3Returns the out-degree of a node in a directed graph.
Parallel edges count separately and a self-loop contributes one. Throws a
GraphError for an undirected graph or missing node.
Signature
declare const outDegree: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => number; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): number;}outgoingEdges
Returns the indices of outgoing edges for a node in a directed graph.
Parallel edges and self-loops are returned separately in adjacency order.
Throws a GraphError for an undirected graph or missing node.
Signature
declare const outgoingEdges: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => Array<number>; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>;}predecessors
Returns the incoming neighbor node indices for a node in a directed graph.
When to use
Use when you need the nodes that reach a node by following incoming edges in a directed graph.
Each node appears once in first incoming edge occurrence order. A self-loop contributes the queried node once.
Gotchas
Throws a GraphError when used with an undirected graph. A missing node
returns an empty array.
See
- successors for outgoing neighbors in a directed graph
- neighbors for generic neighbor lookup across graph kinds
Signature
declare const predecessors: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => Array<number>; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>;}successors
Returns the outgoing neighbor node indices for a node in a directed graph.
When to use
Use when you need the nodes reached by following outgoing edges from a node in a directed graph.
Each node appears once in first outgoing edge occurrence order. A self-loop contributes the queried node once.
Gotchas
Throws a GraphError when used with an undirected graph. A missing node
returns an empty array.
See
- predecessors for incoming neighbors in a directed graph
- neighbors for generic neighbor lookup across graph kinds
Signature
declare const successors: { (nodeIndex: number): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => Array<number>; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, nodeIndex: number): Array<number>;}Guards
Returns true if a value has the graph runtime type identifier, narrowing
it to an immutable or mutable graph.
When to use
Use to narrow an unknown value before treating it as a graph value.
Gotchas
This guard checks the shared graph runtime type identifier and does not distinguish immutable graphs from mutable graphs or directed graphs from undirected graphs.
Signature
declare function isGraph<N = unknown, E = unknown, T extends Kind = Kind, U = never>(u: U | Graph<N, E, T> | MutableGraph<N, E, T>): u is Graph<N, E, T> | MutableGraph<N, E, T>Iterators
Creates a lazy breadth-first traversal iterator from the configured start nodes.
When to use
Use when visiting nodes in increasing unweighted distance from the start nodes.
Details
If no start nodes are supplied, the iterator is empty. The direction option
chooses whether to follow outgoing or incoming edges. The radius option
limits traversal by edge distance from the start nodes. It accepts
non-negative integers and Infinity; omitting it means unbounded traversal.
Gotchas
An invalid radius or missing start node throws a GraphError. Traversing a
mutable graph captures a snapshot when iteration begins; later mutations are
not observed by that iterator.
See
- dfs for branch-first traversal
- unweightedDistances for collecting hop counts
Signature
declare const bfs: { (config?: SearchConfig): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => NodeWalker<N>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config?: SearchConfig): NodeWalker<N>;}Example
(Traversing breadth-first)
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 nodeArray.from(Graph.indices(Graph.bfs(graph, { start: [0] }))) // => [0, 1, 2]
Array.from(Graph.indices(Graph.bfs(graph))) // => []Creates a lazy depth-first traversal iterator from the configured start nodes.
When to use
Use when exploring one branch deeply before visiting sibling branches.
Details
If no start nodes are supplied, the iterator is empty. The direction option
chooses whether to follow outgoing or incoming edges. The radius option
limits traversal by edge distance from the start nodes. It accepts
non-negative integers and Infinity; omitting it means unbounded traversal.
Gotchas
An invalid radius or missing start node throws a GraphError. Traversing a
mutable graph captures a snapshot when iteration begins; later mutations are
not observed by that iterator.
See
- bfs for traversal in increasing hop distance
- dfsPostOrder for emitting descendants before ancestors
Signature
declare const dfs: { (config?: SearchConfig): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => NodeWalker<N>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config?: SearchConfig): NodeWalker<N>;}Example
(Traversing depth-first)
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 nodeArray.from(Graph.indices(Graph.dfs(graph, { start: [0] }))) // => [0, 1, 2]
Array.from(Graph.indices(Graph.dfs(graph))) // => []dfsPostOrder
Creates a lazy depth-first postorder traversal iterator from the configured start nodes.
When to use
Use when reachable descendants must be emitted before the nodes that lead to them.
Details
Nodes are emitted after their reachable descendants have been processed. If
no start nodes are supplied, the iterator is empty. The direction option
chooses whether to follow outgoing or incoming edges. The radius option
limits traversal by edge distance from the start nodes. It accepts
non-negative integers and Infinity; omitting it means unbounded traversal.
With a finite radius, a bounded breadth-first pass first determines
shortest-distance membership before nodes are emitted in postorder.
Gotchas
Invalid radii and missing start nodes throw a GraphError. Traversing a
mutable graph captures a snapshot when iteration begins; later mutations are
not observed by that iterator.
See
- dfs for emitting nodes when first visited
Signature
declare const dfsPostOrder: { (config?: SearchConfig): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => NodeWalker<N>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config?: SearchConfig): NodeWalker<N>;}Example
(Traversing in postorder)
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 parentsArray.from(Graph.indices(Graph.dfsPostOrder(graph, { start: [0] }))) // => [1, 2, 0]Creates a walker over all edge index and edge entries in the graph.
Details
Entries follow graph edge order and include all edges regardless of
connectivity. Use indices or values to project one side of each entry.
Gotchas
Mutable graphs are not snapshotted; mutations may affect the remaining iteration.
Signature
declare function edges<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): EdgeWalker<E>Example
(Iterating all edges)
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)})
Array.from(Graph.indices(Graph.edges(graph))) // => [0, 1]Returns an iterator over [index, data] entries in the walker.
Signature
declare function entries<T, N>(walker: Walker<T, N>): Iterable<[T, N]>Example
(Iterating walker entries)
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] })Array.from(Graph.entries(dfs)) // => [[0, "A"], [1, "B"]]Creates an iterator over external nodes (nodes without edges in the specified direction).
When to use
Use when locating sources, sinks, or isolated boundary nodes.
Details
External nodes have no outgoing edges (direction: "outgoing") or no
incoming edges (direction: "incoming").
Gotchas
For undirected graphs, incoming and outgoing adjacency are equivalent, so only isolated nodes are external. Mutable graphs are not snapshotted; mutations may affect the remaining iteration.
Signature
declare const externals: { (config?: ExternalsConfig): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => NodeWalker<N>; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, config?: ExternalsConfig): NodeWalker<N>;}Example
(Iterating external nodes)
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)Array.from(Graph.indices(Graph.externals(graph, { direction: "outgoing" }))) // => [2, 3]
// Nodes with no incoming edges (sources + isolated)Array.from(Graph.indices(Graph.externals(graph, { direction: "incoming" }))) // => [0, 3]Returns an iterator over the indices in the walker.
Signature
declare function indices<T, N>(walker: Walker<T, N>): Iterable<T>Example
(Iterating walker indices)
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] })Array.from(Graph.indices(dfs)) // => [0, 1]Creates a walker over all node index and payload entries in the graph.
Details
Entries follow graph node order and include all nodes regardless of
connectivity. Use indices or values to project one side of each entry.
Gotchas
Mutable graphs are not snapshotted; mutations may affect the remaining iteration.
Signature
declare function nodes<N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>): NodeWalker<N>Example
(Iterating all nodes)
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)})
Array.from(Graph.indices(Graph.nodes(graph))) // => [0, 1, 2]Creates a lazy topological-order iterator for a directed acyclic graph.
When to use
Use when processing dependencies so every predecessor is emitted before the nodes that depend on it.
Details
The iterator uses Kahn's algorithm. Multiple valid orders may exist;
initials prioritizes eligible zero in-degree nodes without excluding other
nodes.
Gotchas
Undirected or cyclic graphs, missing initial nodes, and initial nodes with
incoming edges throw a GraphError. Traversing a mutable graph captures a
snapshot when iteration begins; later mutations are not observed.
See
- isAcyclic for checking the required graph property
Signature
declare const topo: { (config?: TopoConfig): <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">) => NodeWalker<N>; <N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">, config?: TopoConfig): NodeWalker<N>;}Example
(Sorting topologically)
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)})
Array.from(Graph.indices(Graph.topo(graph))) // => [0, 1, 2]Returns an iterator over the values (data) in the walker.
Signature
declare function values<T, N>(walker: Walker<T, N>): Iterable<N>Example
(Iterating walker values)
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] })Array.from(Graph.values(dfs)) // => ["A", "B"]Mapping
Transforms every edge payload in a mutable graph in place.
When to use
Use when updating every edge payload without changing graph structure.
Details
Edge identifiers and endpoints are preserved.
Gotchas
This function mutates in place, returns void, and cannot change the edge
payload type. The mapping function may query the graph, but cannot mutate or
finalize the same graph while it runs.
See
- updateEdge for updating one edge
- filterMapEdges for mapping while removing edges
Signature
declare function mapEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: E) => E): voidExample
(Mapping edge data)
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, 10) Graph.addEdge(mutable, b, c, 20) Graph.mapEdges(mutable, (data) => data * 2)})
Option.map(Graph.getEdge(graph, 0), (edge) => edge.data) // => Option.some(20)Transforms every node's data in a mutable graph in place using the provided mapping function.
When to use
Use when updating every node payload without changing graph structure.
Details
Node indices and edges are preserved; only the stored node data is replaced.
Gotchas
This function mutates in place, returns void, and cannot change the node
payload type. The mapping function may query the graph, but cannot mutate or
finalize the same graph while it runs.
See
- updateNode for updating one node
- filterMapNodes for mapping while removing nodes
Signature
declare function mapNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, f: (data: N) => N): voidExample
(Mapping node data)
import { Graph, Option } 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())})
Graph.getNode(graph, 0) // => Option.some("NODE A")Models
AllPairsResult interface
Result of an all-pairs shortest path computation.
When to use
Use when storing or passing around the complete output of floydWarshall so
callers can look up shortest distances, node and edge paths, and edge data
for any source and target node pair.
Details
Contains distance, node-path, edge-index-path, and edge-data maps keyed by
source and target node indices. Unreachable pairs have distance Infinity,
path null, and empty edge and cost arrays.
Signature
interface AllPairsResult<E> { readonly costs: Map<number, Map<number, Array<E>>>; readonly distances: Map<number, Map<number, number>>; readonly edges: Map<number, Map<number, Array<number>>>; readonly paths: Map<number, Map<number, Array<number> | null>>;}BipartiteMatch interface
A pair of matched nodes and the edge that realizes the match.
Details
left and right refer to the bipartition derived by
maximumBipartiteMatching, not to the stored edge orientation.
Signature
interface BipartiteMatch { readonly edge: number; readonly left: number; readonly right: number;}CycleResult interface
A cycle witness containing a closed node path and its traversed edges.
Details
path repeats its first node at the end, so edges.length is always
path.length - 1.
Signature
interface CycleResult { readonly edges: Array<number>; readonly path: Array<number>;}DirectedGraph type
Immutable graph type for source-to-target relationships.
When to use
Use as the immutable graph type when edge direction is part of the model and traversal or neighbor queries should follow source-to-target edges.
Details
DirectedGraph<N, E> is a Graph<N, E, "directed"> with node data of type
N and edge data of type E.
See
- directed for constructing directed graphs
- UndirectedGraph for graphs whose edges connect both endpoints
Signature
type DirectedGraph<N, E> = Graph<N, E, "directed">Direction of directed edges relative to a node.
Details
"outgoing" selects edges whose source is the node, while "incoming"
selects edges whose target is the node.
Signature
type Direction = "outgoing" | "incoming"Represents edge data containing source, target, and user data.
When to use
Use as the graph edge value that carries source node, target node, and stored edge data together.
Signature
interface Edge<out E> { readonly data: E; readonly source: number; readonly target: number;}Edge index for edge identification using plain numbers.
When to use
Use when you need to keep the identifier for a graph edge so you can later read, update, remove, or compare that edge.
Gotchas
An EdgeIndex is an identifier, not an array offset. Removed edge
identifiers are not reused.
See
- NodeIndex for node identifiers instead of edge identifiers
Signature
type EdgeIndex = numberEdgeWalker type
Type alias for edge iteration using Walker. EdgeWalker is represented as Walker<EdgeIndex, Edge>.
When to use
Use to type helpers or parameters that consume edge iterators returned by
Graph APIs, where each item is keyed by an EdgeIndex and carries the
full Edge.
See
- Walker for the generic lazy iterator wrapper
- NodeWalker for node iterators
- edges for creating edge walkers
Signature
type EdgeWalker<E> = Walker<EdgeIndex, Edge<E>>Companion namespace containing type-level metadata for immutable graphs.
Immutable graph interface.
When to use
Use as the immutable graph model for code that queries, traverses, transforms, or analyzes graph structure without mutating it.
Gotchas
After a graph is hashed, its transitively contained node and edge payloads used by hashing must remain immutable, as with other Effect values.
See
- MutableGraph for the mutable counterpart used inside mutation scopes
Signature
interface Graph<out N, out E, T extends Kind = "directed"> extends Proto<N, E> { readonly mutable: false; readonly type: T;}IndexedEdge interface
An edge and its stable index in a graph snapshot.
Signature
interface IndexedEdge<out E> extends Edge<E> { readonly index: number;}IndexedNode interface
A node and its stable index in a graph snapshot.
Signature
interface IndexedNode<out N> { readonly data: N; readonly index: number;}Graph type for distinguishing directed and undirected graphs.
When to use
Use when writing graph-polymorphic types or helpers that need to preserve whether a graph is directed or undirected.
Signature
type Kind = "directed" | "undirected"MaximumFlowResult interface
Maximum flow value, per-edge flows, and a corresponding minimum cut.
Details
flows contains every original edge, including zero-flow edges. cut
contains the crossing edge identifiers of the corresponding minimum cut.
Signature
interface MaximumFlowResult { readonly cut: Array<number>; readonly flows: Map<number, number>; readonly value: number;}MermaidDiagramType type
Mermaid diagram types for different visualization formats.
Details
Specifies the Mermaid diagram syntax to use:
flowchart: For directed graphs with arrows (A --> B)graph: For undirected graphs with lines (A --- B)
When not specified, automatically selects based on graph type: directed graphs use "flowchart", undirected graphs use "graph".
Signature
type MermaidDiagramType = "flowchart" | "graph"Example
(Selecting Mermaid diagram types)
import type { Graph } from "effect"
// Force flowchart format (even for undirected graphs)const flowchartOptions: Graph.MermaidOptions<string, string> = { diagramType: "flowchart"}
// Force graph format (shows undirected connections)const graphOptions: Graph.MermaidOptions<string, string> = { diagramType: "graph"}
// Auto-detection (recommended, default behavior)const autoOptions: Graph.MermaidOptions<string, string> = {}Array.of(flowchartOptions.diagramType, graphOptions.diagramType, autoOptions.diagramType) // => ["flowchart", "graph", undefined]MermaidDirection type
Mermaid diagram direction types for controlling layout orientation.
Details
Determines the flow direction of nodes and edges in the diagram:
TB/TD: Top to Bottom (vertical layout, default)BT: Bottom to Top (reverse vertical)LR: Left to Right (horizontal layout)RL: Right to Left (reverse horizontal)
Signature
type MermaidDirection = "TB" | "TD" | "BT" | "RL" | "LR"Example
(Configuring Mermaid directions)
import type { Graph } from "effect"
// Horizontal workflow diagramconst horizontalOptions: Graph.MermaidOptions<string, string> = { direction: "LR"}
// Vertical hierarchy (default)const verticalOptions: Graph.MermaidOptions<string, string> = { direction: "TB"}
// Bottom-up flowconst bottomUpOptions: Graph.MermaidOptions<string, string> = { direction: "BT"}Array.of(horizontalOptions.direction, verticalOptions.direction, bottomUpOptions.direction) // => ["LR", "TB", "BT"]MermaidNodeShape type
Mermaid node shape types for diagram visualization.
Details
Each shape produces different visual representations in Mermaid diagrams:
rectangle: Standard rectangular nodesA["label"]rounded: Rounded rectangular nodesA("label")circle: Circular nodesA(("label"))diamond: Diamond-shaped nodesA{"label"}hexagon: Hexagonal nodesA{{"label"}}stadium: Stadium-shaped nodesA(["label"])subroutine: Subroutine-style nodesA[["label"]]cylindrical: Cylindrical database-style nodesA[("label")]
Signature
type MermaidNodeShape = "rectangle" | "rounded" | "circle" | "diamond" | "hexagon" | "stadium" | "subroutine" | "cylindrical"Example
(Selecting Mermaid node shapes)
import type { Graph } from "effect"
// Shape selector function for different node typesconst shapeSelector = (nodeData: string): Graph.MermaidNodeShape => { if (nodeData.includes("start") || nodeData.includes("end")) return "circle" if (nodeData.includes("decision")) return "diamond" if (nodeData.includes("process")) return "rectangle" if (nodeData.includes("data")) return "cylindrical" return "rounded"}
const options: Graph.MermaidOptions<string, string> = { nodeShape: shapeSelector}options.nodeShape?.("decision") // => "diamond"MinimumCutResult interface
Minimum cut value, crossing edges, and residual-reachability partitions.
Details
source contains nodes residual-reachable from the configured source and
target contains the remaining nodes.
Signature
interface MinimumCutResult { readonly edges: Array<number>; readonly source: Array<number>; readonly target: Array<number>; readonly value: number;}MutableDirectedGraph type
Mutable directed graph type alias.
When to use
Use when annotating a temporary graph value that can be changed in place and whose edges have source-to-target direction.
See
- MutableGraph for the generic mutable graph type
- MutableUndirectedGraph for mutable graphs without edge direction
Signature
type MutableDirectedGraph<N, E> = MutableGraph<N, E, "directed">MutableGraph
Companion namespace containing type-level metadata for scoped mutable graphs.
MutableGraph interface
Mutable graph interface.
When to use
Use when adding, removing, or updating nodes and edges inside a graph mutation scope.
Gotchas
A callback invoked by another graph operation may query the same mutable
graph, but cannot mutate or finalize it. Mutation is allowed in callbacks
passed to graph constructors and mutate, where mutation is the purpose.
See
Signature
interface MutableGraph<in out N, in out E, T extends Kind = "directed"> extends Iterable<readonly [NodeIndex, N]>, Equal, Pipeable, Inspectable { readonly "~effect/collections/Graph": Variance<N, E>; readonly mutable: true; readonly type: T;}MutableUndirectedGraph type
Mutable undirected graph type alias.
When to use
Use when annotating a temporary graph value that can be changed in place and whose edges connect both endpoints without direction.
See
- MutableDirectedGraph for mutable graphs with directed edges
- MutableGraph for the generic mutable graph type
Signature
type MutableUndirectedGraph<N, E> = MutableGraph<N, E, "undirected">Node index for node identification using plain numbers.
When to use
Use when storing or passing the stable identifier of a graph node between
Graph operations.
Details
addNode allocates node identifiers from the graph's next node index.
Gotchas
A NodeIndex is an identifier, not an array offset. Removed node identifiers
are not reused.
See
- EdgeIndex for edge identifiers instead of node identifiers
Signature
type NodeIndex = numberNodeWalker type
Type alias for node iteration using Walker. NodeWalker is represented as Walker<NodeIndex, N>.
When to use
Use as the shared node walker type returned by graph traversal and node listing APIs.
See
- Walker for the generic lazy iterator wrapper
- EdgeWalker for edge iterators
Signature
type NodeWalker<N> = Walker<NodeIndex, N>PathResult interface
Result of a shortest path computation.
When to use
Use to read the successful source-to-target shortest path returned by path-finding algorithms, including the ordered node and edge indices, total distance, and traversed edge data.
Details
Contains the node-index path, the traversed edge indices, the total numeric distance, and the edge data encountered along the path.
Gotchas
costs contains original edge data, not the numeric output of the cost
function unless the edge data is numeric.
Signature
interface PathResult<E> { readonly costs: Array<E>; readonly distance: number; readonly edges: Array<number>; readonly path: Array<number>;}PathWalker interface
A repeatable lazy iterable of edge-aware graph paths.
When to use
Use as the lazy result of graph path-enumeration functions.
Details
Each fresh iterator repeats the path enumeration.
Signature
interface PathWalker<E> extends Iterable<PathResult<E>> {}Active indexed structure used to reconstruct an immutable graph.
When to use
Use when serializing or importing graph structure while preserving active node and edge identifiers.
Details
Node and edge indexes must be non-negative safe integers in strictly increasing order. Every edge endpoint must reference an indexed node.
Gotchas
A snapshot records only active identifiers, not allocator history. After reconstruction, new identifiers continue after the greatest active index.
See
- fromSnapshot for reconstructing a graph
Signature
interface Snapshot<out N, out E, out T extends Kind> { readonly edges: readonly Array<IndexedEdge<E>>; readonly nodes: readonly Array<IndexedNode<N>>; readonly type: T;}TraversalDirection type
Controls how traversal follows directed edges.
Details
"outgoing" follows edges from source to target, "incoming" follows them
from target to source, and "undirected" allows traversal in either
direction.
Signature
type TraversalDirection = Direction | "undirected"Example
(Traversing by direction)
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, a, c, "A-C")})
Array.from(Graph.indices(Graph.bfs(graph, { start: [0], direction: "outgoing" }))) // => [0, 1, 2]Array.from(Graph.indices(Graph.bfs(graph, { start: [1], direction: "incoming" }))) // => [1, 0]Array.from(Graph.indices(Graph.bfs(graph, { start: [1], direction: "undirected" }))) // => [1, 0, 2]UndirectedGraph type
Immutable graph type for relationships without source-to-target direction.
When to use
Use when modeling relationships where each edge connects both endpoints without a source-to-target direction.
Details
UndirectedGraph<N, E> is a Graph<N, E, "undirected">.
See
- undirected for constructing undirected graphs
- DirectedGraph for graphs whose edges have source-to-target direction
Signature
type UndirectedGraph<N, E> = Graph<N, E, "undirected">Represents an iterable wrapper used by graph traversal and listing APIs.
Details
A Walker yields [index, data] pairs lazily and can be viewed as just the
indices, just the values, or mapped entries with indices, values,
entries, and visit.
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
(Working with node walkers)
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 NodeWalkerconst dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
// Common interface for working with node iterablesfunction processNodes<N>(nodeIterable: Graph.NodeWalker<N>): Array<number> { return Array.from(Graph.indices(nodeIterable))}
// Access node data using values() or entries()Array.from(Graph.values(dfsNodes)) // => ["A", "B"]Array.from(Graph.entries(allNodes)) // => [[0, "A"], [1, "B"]]Mutations
Adds a new edge to a mutable graph and returns its index.
When to use
Use to connect two existing nodes in a mutable graph while storing edge data and receiving the new edge identifier.
Details
Self-loops and parallel edges are allowed. Undirected graphs retain the
supplied source and target orientation in the stored Edge, while traversal
and neighbor queries treat the connection as bidirectional.
Gotchas
The source and target nodes must already exist in the mutable graph; missing
endpoints throw a GraphError.
See
Signature
declare function addEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, source: number, target: number, data: E): numberExample
(Adding edges)
import { Graph } from "effect"
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) // => 0})Adds a new node to a mutable graph and returns its index.
When to use
Use to allocate a new node in a mutable graph before storing edges or querying it by index.
Details
The returned index is allocated from the graph's next node index. The mutable graph stores the node data and initializes empty incoming and outgoing edge indexes for the new node.
Gotchas
NodeIndex values are identifiers and are not reused after removals.
See
Signature
declare function addNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, data: N): numberExample
(Adding nodes)
import { Graph } from "effect"
Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Node A") // => 0 Graph.addNode(mutable, "Node B") // => 1})beginMutation
Creates a mutable copy of an immutable graph for a manual mutation scope.
When to use
Use when a mutation scope must span code that cannot be expressed as one
mutate callback.
Gotchas
The graph structure is copied, but node and edge payload objects remain
shared by reference. Always finish the scope with endMutation; prefer
mutate when a callback is sufficient.
See
- endMutation for finalizing the mutable graph
- mutate for automatically scoped mutation
Signature
declare function beginMutation<N, E, T extends Kind = "directed">(graph: Graph<N, E, T>): MutableGraph<N, E, T>Example
(Beginning a mutation scope)
import { Graph } from "effect"
const graph = Graph.directed<string, number>()const mutable = Graph.beginMutation(graph)// Now mutable can be safely modified without affecting original graphArray.of(Graph.nodeCount(mutable), Graph.nodeCount(graph)) // => [0, 0]endMutation
Converts a mutable graph back to an immutable graph, ending the mutation scope.
When to use
Use to finish a mutation scope opened with beginMutation.
Gotchas
Finalization is terminal. Later public mutation operations on the same
mutable handle fail with a GraphError.
See
- beginMutation for opening a manual mutation scope
- mutate for automatically scoped mutation
Signature
declare function endMutation<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>): Graph<N, E, T>Example
(Ending a mutation scope)
import { Graph } from "effect"
const graph = Graph.directed<string, number>()const mutable = Graph.beginMutation(graph)// ... perform mutations on mutable ...Graph.nodeCount(Graph.endMutation(mutable)) // => 0Returns an immutable graph after applying scoped mutations to a structural copy.
When to use
Use for the usual immutable update workflow when several node or edge mutations should be applied together.
Details
The original graph remains structurally unchanged. The mutable callback handle is finalized whether the callback returns or throws.
Gotchas
Payload objects are shared unless the callback replaces them. A callback failure is rethrown after the mutable handle is finalized, and the handle must not escape for later mutation.
See
- beginMutation for opening a manual mutation scope
- endMutation for finalizing a manual mutation scope
Signature
declare const mutate: { <N, E, T extends Kind = "directed">(f: (mutable: MutableGraph<N, E, T>) => undefined): (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>) => undefined): Graph<N, E, T>;}Example
(Applying scoped mutations)
import { Graph } from "effect"
const graph = Graph.directed<string, number>()const newGraph = Graph.mutate(graph, (mutable) => { const nodeA = Graph.addNode(mutable, "A") const nodeB = Graph.addNode(mutable, "B") Graph.addEdge(mutable, nodeA, nodeB, 1)})
Graph.nodeCount(newGraph) // => 2Graph.edgeCount(newGraph) // => 1removeEdge
Removes an edge from a mutable graph.
Gotchas
A missing edge index is ignored.
Signature
declare function removeEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndex: number): voidExample
(Removing an edge)
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)})Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [2, 0]removeEdges
Removes multiple edges from a mutable graph.
When to use
Use when deleting a collection of edges in one mutation pass.
Details
The input is collected before mutation, so it may be backed by an iterator over the same graph.
Gotchas
Missing and duplicate edge indices are ignored. Nodes are never removed.
See
- removeEdge for removing one edge
Signature
declare function removeEdges<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndices: Iterable<number>): voidremoveNode
Removes a node and all its incident edges from a mutable graph.
Gotchas
A missing node index is ignored.
Signature
declare function removeNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, nodeIndex: number): voidExample
(Removing a node)
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)})Array.of(Graph.nodeCount(result), Graph.edgeCount(result)) // => [1, 0]removeNodes
Removes multiple nodes and all their incident edges from a mutable graph.
When to use
Use when deleting a collection of nodes in one mutation pass.
Details
The input is collected before mutation, so it may be backed by an iterator over the same graph.
Gotchas
Missing and duplicate node indices are ignored. Removing a node also removes all of its incident edges.
See
- removeNode for removing one node
Signature
declare function removeNodes<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, nodeIndices: Iterable<number>): voidPredicates
Checks whether an edge exists between two nodes in the graph.
Details
Directed graphs test only source to target; undirected graphs accept
either stored orientation. Parallel edges still produce one boolean result.
Gotchas
Returns false when either node does not exist.
See
- edgesBetween for all matching edge identifiers
Signature
declare const hasEdge: { (source: number, target: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => boolean; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, source: number, target: number): boolean;}Example
(Checking edge existence)
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)})
Graph.hasEdge(graph, 0, 1) // => trueGraph.hasEdge(graph, 0, 2) // => falseChecks whether a node with the given index exists in the graph.
Signature
declare const hasNode: { (nodeIndex: number): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => boolean; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, nodeIndex: number): boolean;}Example
(Checking node existence)
import { Graph } from "effect"
const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => { Graph.addNode(mutable, "Node A")})
Graph.hasNode(graph, 0) // => trueGraph.hasNode(graph, 999) // => falseTests whether a target is reachable from a source.
When to use
Use when you only need a reachability boolean rather than distances or a reconstructed path.
Details
Directed traversal is outgoing by default and can be changed with
direction. A node is reachable from itself.
Gotchas
Throws a GraphError when either endpoint does not exist.
See
- unweightedDistances for hop distances to all reachable nodes
- dijkstra for a minimum-cost path
Signature
declare const hasPath: { (source: number, target: number, options?: ReachabilityConfig): <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>) => boolean; <N, E, T extends Kind = "directed">(graph: Graph<N, E, T> | MutableGraph<N, E, T>, source: number, target: number, options?: ReachabilityConfig): boolean;}isConnected
Tests whether an undirected graph has at most one connected component.
When to use
Use when checking undirected connectivity without allocating the component partition.
Gotchas
The empty graph is considered connected. Throws a GraphError when used
with a directed graph.
See
- connectedComponents for the component partition
Signature
declare function isConnected<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): booleanisStronglyConnected
Tests whether a directed graph has at most one strongly connected component.
When to use
Use when checking that every node in a directed graph can reach every other node.
Gotchas
The empty graph is considered strongly connected. Throws a GraphError when
used with an undirected graph.
See
- isWeaklyConnected when edge orientation should be ignored
Signature
declare function isStronglyConnected<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): booleanTests whether a non-empty undirected graph is a tree.
When to use
Use when validating that an undirected graph is connected and has no cycle.
Gotchas
The empty graph is not a tree. Parallel edges and self-loops prevent a graph
from being a tree. Throws a GraphError when used with a directed graph.
Signature
declare function isTree<N, E>(graph: Graph<N, E, "undirected"> | MutableGraph<N, E, "undirected">): booleanisWeaklyConnected
Tests whether a directed graph has at most one weakly connected component.
When to use
Use when checking whether a directed graph is connected after ignoring edge orientation.
Gotchas
The empty graph is considered weakly connected. Throws a GraphError when
used with an undirected graph.
See
- isStronglyConnected when edge orientation must be respected
Signature
declare function isWeaklyConnected<N, E>(graph: Graph<N, E, "directed"> | MutableGraph<N, E, "directed">): booleanProtocols
Common public protocol for graph values.
Details
Contains only the runtime marker and shared protocols. Graph storage is kept
internal; use module functions such as nodes, edges, getNode, and
getEdge to inspect graph contents.
Signature
interface Proto<out N, out E> extends Iterable<readonly [NodeIndex, N]>, Equal, Pipeable, Inspectable { readonly "~effect/collections/Graph": Variance<N, E>;}Set Operations
complement
Returns the complement over the existing node set.
When to use
Use when materializing every relationship that is currently absent between distinct nodes.
Details
Directed graphs add each missing ordered pair. Undirected graphs add each
missing unordered pair once. The createEdge function receives the source
and target node data for each added edge. The result has the same graph kind
as self.
G' = {V, ((V x V) without self-pairs) \ E}
Gotchas
Self-loops are never created. If any edge already connects a pair, parallel complement edges are not added. The result allocates new identifiers.
See
- hasEdge for testing one relationship
Signature
declare const complement: { <N, E>(createEdge: (source: N, target: N) => E): <T extends Kind = "directed">(self: Graph<N, E, T>) => Graph<N, E, T>; <N, E, T extends Kind = "directed">(self: Graph<N, E, T>, createEdge: (source: N, target: N) => E): Graph<N, E, T>;}Example
(Finding missing relationships)
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 result = Graph.complement(graph, (source, target) => `${source}-${target}`)
Graph.edgeCount(result) // => 1Composes two graphs, merging nodes by identity.
When to use
Use when combining graphs that describe overlapping logical entities and should merge those entities by payload or a projected identity.
Details
Nodes and edges present in both graphs use data from that. The result has
the same graph kind as self. Throws a GraphError when the graph kinds do
not match. nodeIdentity and edgeIdentity default to the complete node and
edge data. Edge identity also includes the endpoint identities.
G1 ∪ G2 = {V1 ∪ V2, E1 ∪ E2}
Gotchas
Nodes with equal identities in one input graph are coalesced. The last node supplies the data, and redirected edges can collapse or become self-loops. Parallel edges with equal identities are also coalesced, with the last edge supplying the data. The result allocates new node and edge identifiers.
See
- sum for combining graphs without merging equal nodes
Signature
declare const compose: { <N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>; <N, E, T extends Kind = "directed", NI = N, EI = E>(self: Graph<N, E, T>, that: Graph<N, E, NoInfer<T>>, options?: IdentityOptions<N, E, NI, EI>): Graph<N, E, T>;}Example
(Combining graphs)
import { Graph } from "effect"
const left = Graph.directed<{ id: string }, string>((mutable) => { const a = Graph.addNode(mutable, { id: "A" }) const b = Graph.addNode(mutable, { id: "B" }) Graph.addEdge(mutable, a, b, "A-B")})
const right = Graph.directed<{ id: string }, string>((mutable) => { const b = Graph.addNode(mutable, { id: "B" }) const c = Graph.addNode(mutable, { id: "C" }) Graph.addEdge(mutable, b, c, "B-C")})
const result = Graph.compose(left, right, { nodeIdentity: (node) => node.id})
Graph.nodeCount(result) // => 3Graph.edgeCount(result) // => 2difference
Returns self without edges also present in that.
When to use
Use when retaining all logical nodes from one graph while removing edge relationships also represented by another graph.
Details
All nodes from self are preserved. Edges are matched by endpoint and edge
identities. The result has the same graph kind as self. Throws a
GraphError when the graph kinds do not match. nodeIdentity and
edgeIdentity default to the complete node and edge data.
G1 \ G2 = {V1, E1 \ E2}
Gotchas
Nodes with equal identities in one input graph are coalesced. The last node
supplies the data, and redirected edges can collapse or become self-loops.
If that contains an edge identity, every parallel edge with that identity
is removed from self. The result allocates new node and edge identifiers.
See
- symmetricDifference for retaining edges unique to either graph
Signature
declare const difference: { <N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>; <N, E, T extends Kind = "directed", NI = N, EI = E>(self: Graph<N, E, T>, that: Graph<N, E, NoInfer<T>>, options?: IdentityOptions<N, E, NI, EI>): Graph<N, E, T>;}Example
(Removing shared edges)
import { Graph } from "effect"
const left = 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")})
const right = Graph.directed<string, string>((mutable) => { const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") Graph.addEdge(mutable, b, c, "B-C")})
const result = Graph.difference(left, right)
Graph.nodeCount(result) // => 3Graph.edgeCount(result) // => 1inducedSubgraph
Returns the subgraph induced by a collection of node indices.
When to use
Use when selecting an exact node set while preserving its active node and edge identifiers.
Details
Node and edge indices are preserved. Duplicate input indices are ignored, output ordering follows the original graph, and every edge whose endpoints are both selected is retained.
Gotchas
Throws a GraphError when a selected node does not exist.
See
- neighborhood for selecting nodes by traversal distance
Signature
declare const inducedSubgraph: { (nodeIndices: Iterable<number>): <N, E, T extends Kind = "directed">(self: Graph<N, E, T>) => Graph<N, E, T>; <N, E, T extends Kind = "directed">(self: Graph<N, E, T>, nodeIndices: Iterable<number>): Graph<N, E, T>;}intersection
Returns the intersection of two graphs, matching nodes by identity.
When to use
Use when extracting nodes and edges that represent the same logical structure in both graphs.
Details
Node data comes from self, and edge data comes from that. The result has
the same graph kind as self. Throws a GraphError when the graph kinds do
not match. nodeIdentity and edgeIdentity default to the complete node and
edge data. Edge identity also includes the endpoint identities.
G1 ∩ G2 = {V1 ∩ V2, E1 ∩ E2}
Gotchas
Nodes with equal identities in one input graph are coalesced. The last node supplies the data, and redirected edges can collapse or become self-loops. The result contains at most one edge for each shared edge identity and allocates new node and edge identifiers.
See
- compose for identity-based graph union
Signature
declare const intersection: { <N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>; <N, E, T extends Kind = "directed", NI = N, EI = E>(self: Graph<N, E, T>, that: Graph<N, E, NoInfer<T>>, options?: IdentityOptions<N, E, NI, EI>): Graph<N, E, T>;}Example
(Finding shared structure)
import { Graph } from "effect"
const left = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, "shared")})
const right = Graph.directed<string, string>((mutable) => { const a = Graph.addNode(mutable, "A") const b = Graph.addNode(mutable, "B") Graph.addEdge(mutable, a, b, "shared")})
const result = Graph.intersection(left, right)
Graph.nodeCount(result) // => 2Graph.edgeCount(result) // => 1neighborhood
Returns the induced subgraph containing nodes within a radius of a node.
When to use
Use when extracting a local reachable region around one node.
Details
The radius option is the maximum edge distance from nodeIndex, accepts
non-negative integers and Infinity, and defaults to 1. Invalid radii
throw a GraphError. The direction option controls directed graph
traversal and defaults to "outgoing". The result has the same graph kind
as self and keeps all original edges whose endpoints are both reached.
"undirected" ignores edge direction while finding reachable nodes.
Gotchas
Traversal chooses the nodes, then all original edges between reached nodes are retained. The result is not merely a traversal tree, and it allocates new node and edge identifiers.
See
- inducedSubgraph for selecting nodes while preserving identifiers
Signature
declare const neighborhood: { (nodeIndex: number, options?: NeighborhoodConfig): <N, E, T extends Kind = "directed">(self: Graph<N, E, T>) => Graph<N, E, T>; <N, E, T extends Kind = "directed">(self: Graph<N, E, T>, nodeIndex: number, options?: NeighborhoodConfig): Graph<N, E, T>;}Example
(Getting a local neighborhood)
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")})
const result = Graph.neighborhood(graph, 1, { radius: 1 })
Graph.nodeCount(result) // => 2Returns the disjoint union of two graphs.
When to use
Use when combining graphs while keeping every node distinct, even when node payloads are equal.
Details
Copies all nodes and edges from both graphs without merging equal node data.
The result has the same graph kind as self. Throws a GraphError when the
graph kinds do not match.
G1 + G2 = {disjoint V1 + V2, disjoint E1 + E2}
Gotchas
All node and edge identifiers are newly allocated.
See
- compose for merging overlapping logical nodes by identity
Signature
declare const sum: { <N, E, T extends Kind>(that: Graph<N, E, T>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>; <N, E, T extends Kind>(self: Graph<N, E, T>, that: Graph<N, E, NoInfer<T>>): Graph<N, E, T>;}symmetricDifference
Returns edges present in exactly one of two graphs.
When to use
Use when comparing graphs and retaining relationships unique to either one.
Details
Keeps nodes from both graphs. Overlapping nodes use data from that. The
result has the same graph kind as self. Throws a GraphError when the
graph kinds do not match. nodeIdentity and edgeIdentity default to the
complete node and edge data. Edge identity also includes the endpoint
identities.
G1 Δ G2 = {V1 ∪ V2, (E1 ∪ E2) \ (E1 ∩ E2)}
Gotchas
Edges with different projected identities are distinct. Nodes with equal identities in one input graph are coalesced. The last node supplies the data, and redirected edges can collapse or become self-loops. Parallel edges with equal identities are coalesced before the graphs are compared. The result allocates new node and edge identifiers.
See
- difference for removing only the edges found in another graph
Signature
declare const symmetricDifference: { <N, E, T extends Kind = "directed", NI = N, EI = E>(that: Graph<N, E, T>, options?: IdentityOptions<N, E, NI, EI>): (self: Graph<N, E, NoInfer<T>>) => Graph<N, E, T>; <N, E, T extends Kind = "directed", NI = N, EI = E>(self: Graph<N, E, T>, that: Graph<N, E, NoInfer<T>>, options?: IdentityOptions<N, E, NI, EI>): Graph<N, E, T>;}Example
(Finding differing edges)
import { Graph } from "effect"
const left = 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")})
const right = Graph.directed<string, string>((mutable) => { const b = Graph.addNode(mutable, "B") const c = Graph.addNode(mutable, "C") const d = Graph.addNode(mutable, "D") Graph.addEdge(mutable, b, c, "B-C") Graph.addEdge(mutable, c, d, "C-D")})
const result = Graph.symmetricDifference(left, right)
Graph.nodeCount(result) // => 4Graph.edgeCount(result) // => 2Transforming
Swaps source and target nodes for every edge in a mutable graph.
When to use
Use when reversing every relationship in a directed graph, such as creating a dependency transpose.
Details
Edge identifiers and payloads are preserved.
Gotchas
This operation is a no-op for undirected graphs.
Signature
declare function reverse<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>): voidExample
(Reversing edge directions)
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) // A -> B Graph.addEdge(mutable, b, c, 2) // B -> C Graph.reverse(mutable) // Now B -> A, C -> B})
Option.map(Graph.getEdge(graph, 0), (edge) => edge.source) // => Option.some(1)updateEdge
Updates a single edge's data by applying a transformation function.
When to use
Use when replacing one edge payload while preserving its identifier and endpoints.
Gotchas
A missing edge index is ignored. The transformation may query the graph, but cannot mutate or finalize the same graph while it runs.
Signature
declare function updateEdge<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, edgeIndex: number, f: (data: E) => E): voidExample
(Updating edge data)
import { Graph, Option } 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)})
Option.map(Graph.getEdge(result, 0), (edge) => edge.data) // => Option.some(20)updateNode
Updates a single node's data by applying a transformation function.
When to use
Use when replacing one node payload while preserving its identifier and incident edges.
Gotchas
A missing node index is ignored. The transformation may query the graph, but cannot mutate or finalize the same graph while it runs.
Signature
declare function updateNode<N, E, T extends Kind = "directed">(mutable: MutableGraph<N, E, T>, index: number, f: (data: N) => N): voidExample
(Updating node data)
import { Graph, Option } 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())})
Graph.getNode(graph, 0) // => Option.some("NODE A")