A* Search
Dijkstra pointed at one target with a heuristic hunch about which way the goal lies.
Prerequisites
- Dijkstra's Algorithm —
A*is Dijkstra plus a goal-directed term; every part of it reappears here. - Heaps & Priority Queues — the open set is a priority queue keyed by
f = g + h. - Breadth-First Search — the blind expansion that
A*learns to aim. - Graph Representations — grids, road networks and navmeshes are all just graphs with weights.
The 10-second version
A* is Dijkstra with a sense of direction. Instead of always expanding the node with the smallest cost from the start, it expands the node with the smallest f = g + h, where g is the real cost so far and h is a heuristic guess of the remaining cost to the goal. If h never overestimates the true remaining cost (admissible) and plays nicely along edges (consistent), A* still returns the optimal path — but it touches a tiny fraction of the nodes Dijkstra would, as long as the guess actually knows something. Get the heuristic wrong and you lose optimality; set it to zero and you are back to plain Dijkstra.
Why this existed
By the late 1960s, robots were about to have to find their own way around. At the Stanford Research Institute (SRI), a project was building Shakey — the first mobile robot that could reason about its own actions, named for the way it wobbled when it moved. Shakey needed to plan routes through a building: a goal, a map, and a graph of rooms and doorways. Dijkstra could do it, but it spread out in every direction like water, exploring rooms behind it just as eagerly as rooms in front. That is wasteful when you know where you are going.
Earlier work had already tried to exploit that knowledge. In 1966, Doran and Michie built the Graph Traverser, which used a heuristic estimate to decide what to explore next — fast, but with no guarantee of finding the best path.
Then in 1968, Peter Hart, Nils Nilsson, and Bertram Raphael published "A Formal Basis for the Heuristic Determination of Minimum Cost Paths." They took the heuristic idea and, crucially, proved when it is safe: order by g + h, and as long as the heuristic never overestimates, the answer is optimal. The asterisk in the name was their notation for "this search is admissible with this heuristic."
Why it mattered: it was the first search that was both fast and provably correct. That combination is why, half a century later, A* is still the default pathfinder in game engines, mapping software, and robotics.
Inside the inventor's head
Think about what the three candidate strategies actually do:
- Greedy best-first expands the node with the smallest
halone — it beelines toward the goal. Blazing fast, and wrong: a tempting straight line can lead into a dead end, and greedy search never looks back to the cheaper detour. - Dijkstra expands the smallest
g— the cost so far. Always correct, but blind: it explores in a uniform blob and only stops when it happens to reach the goal. - Neither is what a human navigator does. A person reasons with both: "I have paid this much so far, and the goal still looks that far away."
The pivot is to add them. Order the frontier by f = g + h, and the search is pulled toward the goal while still accounting for the cost already sunk in. Then prove it: if h never exceeds the true remaining cost, then f never exceeds the true total cost of a path through that node, so the search can never be lured past a better route. That is the theorem that makes it a real algorithm instead of a heuristic gamble.
The final approach
The algorithm
- Set
g[source] = 0; push the source into the open set with keyh(source). - Pop the node
uwith the smallestf = g + h. Skip it if the entry is stale. - If
uis the goal, reconstruct the path and stop — with a consistent heuristic, the first goal pop is already optimal. - For each neighbor
vwith edge weightw:tentative = g[u] + w. If it is smaller thang[v], setg[v], computef[v] = g[v] + h(v), and pushv. - Repeat until the goal is popped or the open set empties.
The invariants
- Admissible.
h(n) ≤the true cost fromnto the goal, for every node. This is what guaranteesA*never returns a path longer than optimal. - Consistent (monotonic).
h(u) ≤ w(u, v) + h(v)for every edge. This keepsfnon-decreasing along any path, so no node ever needs re-expanding and the first time the goal is popped, itsgis final. - The special case. Set
h ≡ 0andA*collapses exactly into Dijkstra — which is the cleanest proof thatA*is a generalization, not a different animal.
Heuristic search, step by step
Take four nodes with coordinates S = (0,0), A = (1,0), B = (2,0), G = (3,0), edges S→A = 1, S→B = 5, A→B = 1, A→G = 10, B→G = 1. Use straight-line distance as the heuristic:
| Node | h (estimate to G) |
|---|---|
S | 3 |
A | 2 |
B | 1 |
G | 0 |
Two things worth noticing. First, B was first reached at cost 5, then improved to 2 when A discovered a cheaper route — just like Dijkstra, A* rewrites distances. Second, the heuristic steered the search straight down the line (S, A, B, G) and never wasted effort on the A→G = 10 edge's tempting endpoint. Look at what the shapes of the two searches do on a grid:
It in code
function aStar(adj, h, src, goal) { const g = new Array(adj.length).fill(Infinity); g[src] = 0; const open = [[h[src], src]]; // [f, node] — a heap is faster while (open.length) { open.sort((a, b) => a[0] - b[0]); const [, u] = open.shift(); if (u === goal) return g[goal]; // first pop of the goal is optimal for (const [v, w] of adj[u]) { if (g[u] + w < g[v]) { // relax g[v] = g[u] + w; open.push([g[v] + h[v], v]); } } } return Infinity; // unreachable}Complexity
| Heuristic | Nodes expanded | Time (worst) | Optimal? |
|---|---|---|---|
h ≡ 0 (that is, Dijkstra) | a full disk | O((V + E) log V) | yes |
Admissible h | a directed cone | O((V + E) log V) | yes |
Inconsistent h | a cone, with re-expansions | can blow up | yes, if still admissible |
Greedy (g dropped) | as few as possible | fast, no bound | no |
In the worst case A* is no faster than Dijkstra — a bad heuristic gives you nothing. The whole payoff is the practical node count: on a grid with a decent distance heuristic, A* typically expands a small fraction of what Dijkstra does for the same optimal answer.
Where you meet it today
- Games. Nearly every tile-grid and navmesh pathfinder — StarCraft, Age of Empires, Unity's and Unreal's built-in navigation — is
A*under the hood, usually tuning the heuristic to make units look natural. - Maps and route planning. Real road routers use
A*with a travel-time heuristic, then layer on tricks like contraction hierarchies to stay interactive at country scale. - Robotics. It started with Shakey, and it is still here: mobile robots plan with
A*and its incremental cousins (D*-Lite) when the world changes underneath them. - Puzzle solvers. The 8-puzzle or 15-puzzle with Manhattan distance as the heuristic, and Rubik's-cube solvers with an
IDA*variant, are textbookA*applications.
The heuristic is the whole game, and on a grid the classic admissible one is Manhattan distance — it can never overestimate on a four-directional grid, so it never breaks optimality:
// admissible on a 4-directional grid: never overestimatesconst manhattan = (x, y, gx, gy) => Math.abs(x - gx) + Math.abs(y - gy);Recap
A*orders expansion byf = g + h: the real cost so far plus a guess of the cost remaining.- An admissible
h(never overestimating) keeps the result optimal; a consistenthmeans the first time you pop the goal, it is final. h ≡ 0turnsA*back into Dijkstra; dropginstead and you get greedy best-first, which is fast but not optimal.- Worst-case time matches Dijkstra at
O((V + E) log V), but a good heuristic slashes the nodes actually explored. - Born on Shakey at SRI in 1968, it is now the default pathfinder in games, maps, and robotics.
Next up: Divide & Conquer — the graph track ends here and the paradigm track begins by splitting problems cleanly in half.