Skip to main content

Connectivity

Graph connectivity queries: reachability, components, cycle detection, bipartiteness, and strongly connected components.

Most functions come in two flavours depending on whether the graph is directed or undirected. Check each function's directed / undirected tags to confirm which it operates on.

allVertices parameter

Several functions require allVertices: { V } — the complete list of vertices in the graph. This is needed to find disconnected components and to seed multi-source traversals.

All functions accept either callback form for GetNeighbors — see GraphUtil for details.

Example graphs used in the docs below

-- Undirected: one component A─B─C─D, plus a separate component E─F
--  A ── B ── C ── D        E ── F
local undirAdj = {
    A = {"B"}, B = {"A","C"}, C = {"B","D"}, D = {"C"},
    E = {"F"}, F = {"E"},
}

-- Directed: a cycle A→B→C→A, plus an acyclic edge D→E
--  A ──→ B ──→ C          D ──→ E
--  ▲           │
--  └───────────┘
local dirAdj = {
    A = {"B"}, B = {"C"}, C = {"A"},
    D = {"E"}, E = {},
}

Functions

isConnected

directedundirected
Connectivity.isConnected(
startV,
goalV,
getNeighborsGetNeighbors<V>
) → boolean

Returns true if goal is reachable from start by following edges (directed or undirected depending on your getNeighbors callback).

This is a single-target BFS and short-circuits as soon as goal is found.

print(Connectivity.isConnected("A", "D", getNeighbors)) -- true
print(Connectivity.isConnected("A", "E", getNeighbors)) -- false (different component)

Time complexity: O(V + E) worst case, often much better.

components

undirected
Connectivity.components(
allVertices{V},
getNeighborsGetNeighbors<V>
) → {{V}}

Finds all connected components of an undirected graph and returns them as an array of vertex arrays. Each inner array is one component; vertices within a component are in BFS discovery order.

Pass a bidirectional getNeighbors (each undirected edge appears in both directions) so that every connected group is fully explored.

local comps = Connectivity.components({"A","B","C","D","E","F"}, getNeighbors)
-- { {"A","B","C","D"}, {"E","F"} }  (order may vary)

Time complexity: O(V + E)

hasDirectedCycle

directed
Connectivity.hasDirectedCycle(
allVertices{V},
getNeighborsGetNeighbors<V>
) → boolean

Returns true if the directed graph contains at least one cycle.

-- dirAdj: A→B→C→A forms a cycle
print(Connectivity.hasDirectedCycle({"A","B","C","D","E"}, getNeighbors)) -- true
Directed graphs only

For undirected cycle detection use hasUndirectedCycle.

:::info Internals Uses iterative DFS with a three-colour marking scheme:

  • white (0) — unvisited
  • grey (1) — on the current DFS path (in the recursion stack)
  • black (2) — fully processed

A back edge (grey → grey) indicates a cycle. :::

Time complexity: O(V + E)

hasUndirectedCycle

undirected
Connectivity.hasUndirectedCycle(
allVertices{V},
getNeighborsGetNeighbors<V>
) → boolean

Returns true if the undirected graph contains at least one cycle, using BFS with parent tracking. An edge to an already-visited non-parent vertex indicates a cycle.

-- A-B-C-A forms an undirected cycle
print(Connectivity.hasUndirectedCycle({"A","B","C"}, getNeighbors)) -- true
Undirected graphs only

For directed cycle detection use hasDirectedCycle.

Time complexity: O(V + E)

isBipartite

undirected
Connectivity.isBipartite(
startV,
getNeighborsGetNeighbors<V>
) → boolean

Returns true if the graph component reachable from start is bipartite — its vertices can be divided into two groups such that every edge connects a vertex in one group to a vertex in the other.

Uses BFS two-colouring: colour start with 0 and alternate 0/1 for each discovered vertex. If a neighbour already has the same colour as the current vertex, the graph is not bipartite.

-- A-B-C-D square (even cycle) → bipartite
-- A-B-C triangle (odd cycle)   → not bipartite
print(Connectivity.isBipartite("A", getNeighbors))
Per-component

This only checks the component reachable from start. Call once per component (e.g. using components) to check the full graph.

Time complexity: O(V + E)

stronglyConnectedComponents

directed
Connectivity.stronglyConnectedComponents(
allVertices{V},
getNeighborsGetNeighbors<V>
) → {{V}}

Finds all Strongly Connected Components (SCCs) of a directed graph using Kosaraju's algorithm and returns them as an array of vertex arrays. Within each SCC, every vertex can reach every other vertex.

Kosaraju's requires traversing the graph and its reverse. Because the library's callback design doesn't expose a reverse-edge callback, this function builds the reverse adjacency internally during the first DFS pass.

-- A→B→C→A  (one SCC), D→E (two singleton SCCs)
local sccs = Connectivity.stronglyConnectedComponents(
    {"A","B","C","D","E"}, getNeighbors
)
-- { {"A","B","C"}, {"D"}, {"E"} }  (order may vary)

Time complexity: O(V + E)

Show raw api
{
    "functions": [
        {
            "name": "isConnected",
            "desc": "Returns `true` if `goal` is reachable from `start` by following edges\n(directed or undirected depending on your `getNeighbors` callback).\n\nThis is a single-target BFS and short-circuits as soon as `goal` is found.\n\n```lua\nprint(Connectivity.isConnected(\"A\", \"D\", getNeighbors)) -- true\nprint(Connectivity.isConnected(\"A\", \"E\", getNeighbors)) -- false (different component)\n```\n\n*Time complexity:* `O(V + E)` worst case, often much better.",
            "params": [
                {
                    "name": "start",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "goal",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "directed",
                "undirected"
            ],
            "source": {
                "line": 72,
                "path": "lib/graphutil/src/Connectivity.luau"
            }
        },
        {
            "name": "components",
            "desc": "Finds all **connected components** of an undirected graph and returns them\nas an array of vertex arrays.  Each inner array is one component; vertices\nwithin a component are in BFS discovery order.\n\nPass a bidirectional `getNeighbors` (each undirected edge appears in both\ndirections) so that every connected group is fully explored.\n\n```lua\nlocal comps = Connectivity.components({\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"}, getNeighbors)\n-- { {\"A\",\"B\",\"C\",\"D\"}, {\"E\",\"F\"} }  (order may vary)\n```\n\n*Time complexity:* `O(V + E)`",
            "params": [
                {
                    "name": "allVertices",
                    "desc": "",
                    "lua_type": "{ V }"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ { V } }\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "undirected"
            ],
            "source": {
                "line": 121,
                "path": "lib/graphutil/src/Connectivity.luau"
            }
        },
        {
            "name": "hasDirectedCycle",
            "desc": "Returns `true` if the **directed** graph contains at least one cycle.\n\n```lua\n-- dirAdj: A→B→C→A forms a cycle\nprint(Connectivity.hasDirectedCycle({\"A\",\"B\",\"C\",\"D\",\"E\"}, getNeighbors)) -- true\n```\n\n:::caution Directed graphs only\nFor undirected cycle detection use [`hasUndirectedCycle`](#hasUndirectedCycle).\n:::\n\n:::info Internals\nUses iterative DFS with a three-colour marking scheme:\n- **white** (0) — unvisited\n- **grey** (1) — on the current DFS path (in the recursion stack)\n- **black** (2) — fully processed\n\nA back edge (grey → grey) indicates a cycle.\n:::\n\n*Time complexity:* `O(V + E)`",
            "params": [
                {
                    "name": "allVertices",
                    "desc": "",
                    "lua_type": "{ V }"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "directed"
            ],
            "source": {
                "line": 184,
                "path": "lib/graphutil/src/Connectivity.luau"
            }
        },
        {
            "name": "hasUndirectedCycle",
            "desc": "Returns `true` if the **undirected** graph contains at least one cycle,\nusing BFS with parent tracking.  An edge to an already-visited non-parent\nvertex indicates a cycle.\n\n```lua\n-- A-B-C-A forms an undirected cycle\nprint(Connectivity.hasUndirectedCycle({\"A\",\"B\",\"C\"}, getNeighbors)) -- true\n```\n\n:::caution Undirected graphs only\nFor directed cycle detection use [`hasDirectedCycle`](#hasDirectedCycle).\n:::\n\n*Time complexity:* `O(V + E)`",
            "params": [
                {
                    "name": "allVertices",
                    "desc": "",
                    "lua_type": "{ V }"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "undirected"
            ],
            "source": {
                "line": 246,
                "path": "lib/graphutil/src/Connectivity.luau"
            }
        },
        {
            "name": "isBipartite",
            "desc": "Returns `true` if the graph component reachable from `start` is\n**bipartite** — its vertices can be divided into two groups such that\nevery edge connects a vertex in one group to a vertex in the other.\n\nUses BFS two-colouring: colour `start` with 0 and alternate 0/1 for each\ndiscovered vertex.  If a neighbour already has the same colour as the\ncurrent vertex, the graph is not bipartite.\n\n```lua\n-- A-B-C-D square (even cycle) → bipartite\n-- A-B-C triangle (odd cycle)   → not bipartite\nprint(Connectivity.isBipartite(\"A\", getNeighbors))\n```\n\n:::note Per-component\nThis only checks the component reachable from `start`.  Call once per\ncomponent (e.g. using [`components`](#components)) to check the full graph.\n:::\n\n*Time complexity:* `O(V + E)`",
            "params": [
                {
                    "name": "start",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "undirected"
            ],
            "source": {
                "line": 309,
                "path": "lib/graphutil/src/Connectivity.luau"
            }
        },
        {
            "name": "stronglyConnectedComponents",
            "desc": "Finds all **Strongly Connected Components** (SCCs) of a directed graph\nusing **Kosaraju's algorithm** and returns them as an array of vertex\narrays.  Within each SCC, every vertex can reach every other vertex.\n\nKosaraju's requires traversing the graph *and* its reverse.  Because the\nlibrary's callback design doesn't expose a reverse-edge callback, this\nfunction builds the reverse adjacency internally during the first DFS pass.\n\n```lua\n-- A→B→C→A  (one SCC), D→E (two singleton SCCs)\nlocal sccs = Connectivity.stronglyConnectedComponents(\n    {\"A\",\"B\",\"C\",\"D\",\"E\"}, getNeighbors\n)\n-- { {\"A\",\"B\",\"C\"}, {\"D\"}, {\"E\"} }  (order may vary)\n```\n\n*Time complexity:* `O(V + E)`",
            "params": [
                {
                    "name": "allVertices",
                    "desc": "",
                    "lua_type": "{ V }"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ { V } }\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "directed"
            ],
            "source": {
                "line": 358,
                "path": "lib/graphutil/src/Connectivity.luau"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "Connectivity",
    "desc": "Graph connectivity queries: reachability, components, cycle detection,\nbipartiteness, and strongly connected components.\n\nMost functions come in two flavours depending on whether the graph is\n**directed** or **undirected**.  Check each function's `directed` /\n`undirected` tags to confirm which it operates on.\n\n### `allVertices` parameter\n\nSeveral functions require `allVertices: { V }` — the complete list of\nvertices in the graph.  This is needed to find disconnected components\nand to seed multi-source traversals.\n\nAll functions accept either callback form for `GetNeighbors` — see\n[GraphUtil](/api/GraphUtil) for details.\n\n### Example graphs used in the docs below\n\n```lua\n-- Undirected: one component A─B─C─D, plus a separate component E─F\n--  A ── B ── C ── D        E ── F\nlocal undirAdj = {\n    A = {\"B\"}, B = {\"A\",\"C\"}, C = {\"B\",\"D\"}, D = {\"C\"},\n    E = {\"F\"}, F = {\"E\"},\n}\n\n-- Directed: a cycle A→B→C→A, plus an acyclic edge D→E\n--  A ──→ B ──→ C          D ──→ E\n--  ▲           │\n--  └───────────┘\nlocal dirAdj = {\n    A = {\"B\"}, B = {\"C\"}, C = {\"A\"},\n    D = {\"E\"}, E = {},\n}\n```",
    "source": {
        "line": 44,
        "path": "lib/graphutil/src/Connectivity.luau"
    }
}