Skip to main content

AStar

A* heuristic-guided shortest-path search.

A* finds the shortest weighted path from start to goal using a user-provided admissible heuristic to guide the search. By preferring vertices that appear geometrically (or otherwise) closer to the goal, A* typically expands far fewer vertices than Dijkstra while still guaranteeing an optimal result — provided the heuristic never overestimates the true cost.

Choosing a heuristic

Graph type Good heuristic
2-D grid / world-space Euclidean or Manhattan distance
Road network Straight-line distance
Abstract graph 0 (degrades to Dijkstra)
Admissibility

The heuristic must be admissible — it must never return a value greater than the true cheapest cost to reach goal from vertex. An inadmissible heuristic can cause A* to return a suboptimal path.

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

Example (grid pathfinding)

-- Each node is a Vector2 grid position.
-- Edge weight = 1 (uniform grid), heuristic = Manhattan distance.

local function getNeighbors(pos: Vector2): { Vector2 }
    local neighbors = {}
    for _, dir in { Vector2.new(1,0), Vector2.new(-1,0), Vector2.new(0,1), Vector2.new(0,-1) } do
        local next = pos + dir
        if not walls[next] then
            table.insert(neighbors, next)
        end
    end
    return neighbors
end

local function getWeight(_from: Vector2, _to: Vector2): number
    return 1
end

local function heuristic(v: Vector2, goal: Vector2): number
    return math.abs(v.X - goal.X) + math.abs(v.Y - goal.Y)
end

local result = AStar.shortestPath(
    Vector2.new(0, 0),
    Vector2.new(5, 5),
    getNeighbors,
    getWeight,
    heuristic
)

Functions

shortestPath

directedundirected
AStar.shortestPath(
startV,
goalV,
getNeighborsGetNeighbors<V>,
getWeightGetWeight<V>,
heuristicGetHeuristic<V>,
shouldStopShouldStop<V>?
) → PathResult<V>?

Finds the shortest weighted path from start to goal using A* search guided by heuristic. Returns a PathResult, or nil if goal is unreachable from start.

The heap is keyed on f-score = g(v) + h(v, goal) where g(v) is the known cheapest cost from start to v and h is the heuristic.

local result = AStar.shortestPath(start, goal, getNeighbors, getWeight, heuristic)
if result then
    print("Path cost:", result.cost)
    for _, v in result.path do print(v) end
end

Pass an optional ShouldStop predicate to abort a long-running search — it is checked once per expanded vertex (after the goal check, so a within-budget goal is never abandoned) and returning true makes the search stop and return nil:

-- Give up after half a second.
local deadline = os.clock() + 0.5
local result = AStar.shortestPath(start, goal, getNeighbors, getWeight, heuristic, function()
    return os.clock() > deadline
end)
-- result == nil if the deadline hit before the goal was reached

Time complexity: O((V + E) log V) in the worst case, but typically much better in practice due to heuristic pruning.

Show raw api
{
    "functions": [
        {
            "name": "shortestPath",
            "desc": "Finds the shortest weighted path from `start` to `goal` using A\\* search\nguided by `heuristic`.  Returns a [PathResult](/api/GraphUtil#PathResult),\nor `nil` if `goal` is unreachable from `start`.\n\nThe heap is keyed on **f-score** = `g(v) + h(v, goal)` where `g(v)` is\nthe known cheapest cost from `start` to `v` and `h` is the heuristic.\n\n```lua\nlocal result = AStar.shortestPath(start, goal, getNeighbors, getWeight, heuristic)\nif result then\n    print(\"Path cost:\", result.cost)\n    for _, v in result.path do print(v) end\nend\n```\n\nPass an optional [ShouldStop](/api/GraphUtil#ShouldStop) predicate to abort a\nlong-running search — it is checked once per expanded vertex (after the goal\ncheck, so a within-budget goal is never abandoned) and returning `true` makes\nthe search stop and return `nil`:\n\n```lua\n-- Give up after half a second.\nlocal deadline = os.clock() + 0.5\nlocal result = AStar.shortestPath(start, goal, getNeighbors, getWeight, heuristic, function()\n    return os.clock() > deadline\nend)\n-- result == nil if the deadline hit before the goal was reached\n```\n\n*Time complexity:* `O((V + E) log V)` in the worst case, but typically\nmuch better in practice due to heuristic pruning.",
            "params": [
                {
                    "name": "start",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "goal",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                },
                {
                    "name": "getWeight",
                    "desc": "",
                    "lua_type": "GetWeight<V>"
                },
                {
                    "name": "heuristic",
                    "desc": "",
                    "lua_type": "GetHeuristic<V>"
                },
                {
                    "name": "shouldStop",
                    "desc": "",
                    "lua_type": "ShouldStop<V>?\n"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "PathResult<V>?\n"
                }
            ],
            "function_type": "static",
            "tags": [
                "directed",
                "undirected"
            ],
            "source": {
                "line": 117,
                "path": "lib/graphutil/src/AStar.luau"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "AStar",
    "desc": "A\\* heuristic-guided shortest-path search.\n\nA\\* finds the shortest weighted path from `start` to `goal` using a\nuser-provided **admissible heuristic** to guide the search.  By\npreferring vertices that appear geometrically (or otherwise) closer to\nthe goal, A\\* typically expands far fewer vertices than Dijkstra while\nstill guaranteeing an optimal result — provided the heuristic never\noverestimates the true cost.\n\n### Choosing a heuristic\n\n| Graph type | Good heuristic |\n| --- | --- |\n| 2-D grid / world-space | Euclidean or Manhattan distance |\n| Road network | Straight-line distance |\n| Abstract graph | `0` (degrades to Dijkstra) |\n\n:::caution Admissibility\nThe heuristic **must be admissible** — it must never return a value\ngreater than the true cheapest cost to reach `goal` from `vertex`.\nAn inadmissible heuristic can cause A\\* to return a suboptimal path.\n:::\n\nAll functions accept either callback form for `GetNeighbors` — see\n[GraphUtil](/api/GraphUtil) for details.\n\n### Example (grid pathfinding)\n\n```lua\n-- Each node is a Vector2 grid position.\n-- Edge weight = 1 (uniform grid), heuristic = Manhattan distance.\n\nlocal function getNeighbors(pos: Vector2): { Vector2 }\n    local neighbors = {}\n    for _, dir in { Vector2.new(1,0), Vector2.new(-1,0), Vector2.new(0,1), Vector2.new(0,-1) } do\n        local next = pos + dir\n        if not walls[next] then\n            table.insert(neighbors, next)\n        end\n    end\n    return neighbors\nend\n\nlocal function getWeight(_from: Vector2, _to: Vector2): number\n    return 1\nend\n\nlocal function heuristic(v: Vector2, goal: Vector2): number\n    return math.abs(v.X - goal.X) + math.abs(v.Y - goal.Y)\nend\n\nlocal result = AStar.shortestPath(\n    Vector2.new(0, 0),\n    Vector2.new(5, 5),\n    getNeighbors,\n    getWeight,\n    heuristic\n)\n```",
    "source": {
        "line": 68,
        "path": "lib/graphutil/src/AStar.luau"
    }
}