To find the shortest route to one destination on a road graph, Dijkstra settles nodes in expanding rings of cost-from-source: reaching a target 10 km east, it also settles nodes 10 km north, west, and south first. Almost none of that work touches the optimal path. A* keeps the same cost accounting but reorders the frontier by an estimate of total path cost, f(n) = g(n) + h(n)g(n) is the exact cost already paid to reach n, and h(n) is a heuristic estimate of the cost still remaining to the goal. Popping the smallest-f node first pulls the search toward the target, collapsing that settled disc into a narrow corridor.

The saving holds only when h never overestimates the remaining cost. A heuristic that underestimates keeps A* honest; one that overestimates can make it commit to a node that looks close but is not, and return a longer path with no error raised. Dijkstra is the degenerate case h ≡ 0 — no goal information, uniform rings, still optimal.

Core condition: single target + an admissible h (never overestimates remaining cost) → order the frontier by f = g + h → optimal path while settling a fraction of Dijkstra’s nodes.

The decisive step is which node leaves the frontier next, and how h skews that choice toward the goal.

Visualization pending

Planned StepTrace: a frontier ordered by f = g + h, expanding the lowest-f node first, the heuristic biasing expansion toward the goal. The decisive frame is a node with small g but large h losing its turn to a node deeper along the goal direction. No matching renderer exists in engine.js yet.

Why f = g + h stays optimal

Each iteration pops the frontier node with the smallest f, relaxes its outgoing edges, and pushes any neighbor whose g improves; g[source] = 0 and f[source] = h(source) seed the search. Two properties of h decide whether the result is correct.

Admissibilityh(n) never exceeds the true remaining cost h*(n). This alone makes A* return an optimal path. When the goal is popped, h(goal) = 0, so its f equals its g: the exact cost of the path found. Any other frontier node n has f(n) = g(n) + h(n) ≤ g(n) + h*(n), a lower bound on the best path through n; if that lower bound is no smaller than the goal’s cost, no cheaper path is hiding on the frontier. Underestimating is safe — it only makes A* inspect a node sooner than strictly necessary.

Consistency (monotonicity)h(n) ≤ cost(n, n') + h(n') for every edge (n, n'), with h(goal) = 0. Consistency implies admissibility and adds a stronger guarantee: f never decreases along a path, so the first time a node is popped its g is already optimal. Graph-search A* can then move that node to a closed set and never reconsider it — each node is expanded at most once.

The pull is concrete: on a 4-connected grid with Manhattan h, a node reached in g = 3 that sits toward the goal (h = 2, f = 5) is popped before an equal-cost node reached in g = 3 that faces away (h = 5, f = 8). Dijkstra ranks both by g alone and expands the second as readily as the first. That h term is the whole difference between a corridor and a disc, and setting h ≡ 0 erases it — which is exactly what turns A* back into Dijkstra.

Complexity

CaseTime (node expansions)Auxiliary spaceCause
BestΘ(L), L = optimal-path lengthO(nodes stored)h equals the true remaining cost, so A* expands only nodes on the optimal path.
Typicalbetween Θ(L) and O(b^L)O(nodes stored)An informative admissible h prunes off-path branches; the effective branching factor drops below the raw b.
WorstO(b^L)O(b^L)h carries no goal information (h ≡ 0, uninformed search); every node cheaper than the goal is expanded.

Each expansion also does a heap pop plus edge relaxations, a log(frontier) factor over the raw expansion counts. On an explicit finite graph those counts are capped by the graph itself: a consistent heuristic expands each of the V nodes at most once, giving O((V + E) log V) — precisely Dijkstra’s bound, which is what h ≡ 0 reduces to. The exponential figures belong to implicit state spaces generated on the fly, where quality of h is the only thing bounding the search. Space is the operational limit in either setting: A* retains every generated node across the open and closed sets, so memory, not time, is what fails first on large maps.

When the heuristic breaks the guarantee

An inadmissible h overestimates the remaining cost for at least one node somewhere in the graph. That overestimate is harmless where it lands off the optimal path and never wins a pop. Optimality breaks only when an inflated f pre-empts the true optimal path — a node on that path (or one whose f should have been popped before the goal) is delayed, so A* pops the goal through a cheaper-looking detour first. It returns a path, just not the cheapest, and signals nothing. Weighted A* makes exactly this trade deliberately: f = g + ε·h with ε > 1 scales the heuristic up, expanding far fewer nodes for a path guaranteed within a factor ε of optimal. ε = 1 is exact A*; ε → ∞ approaches greedy behavior.

An admissible but inconsistent h keeps optimality for the tree-search form but breaks the single-expansion property. Because f can dip along a path, a shorter g to an already-closed node can surface later. Graph-search A* that refuses to revisit closed nodes then finalizes that node with a non-optimal g, corrupting every path routed through it. The remedy is reopening — pulling the node back onto the frontier when a cheaper g appears — which restores optimality at the cost of the re-expansions consistency would have avoided.

The binding limit is memory. A* holds every generated node across the open frontier and the closed set, O(nodes stored), and on a large state space that exhausts memory long before time. IDA* trades it back: an iterative-deepening variant that keeps only the current path (O(L) memory) and re-expands nodes across successive f-cost thresholds. Weighted A* attacks the same limit from the other side, shrinking the frontier by biasing toward the goal at a bounded loss of optimality.

Reference drawer

Questions

References