1. Home
  2. AI & Machine Learning
  3. A* Search

A* Search

The path-finding algorithm behind games and maps. See how f = g + h focuses the search on the goal — and compare it with Dijkstra and greedy search on a 3D maze.

Interactive 3DIntermediate13 min readAI/MLUpdated

Drag to rotate · Right-drag to pan · Click, then scroll to zoom · Space play · ←→ step

What's happening

Pseudocode

    Try this in the 3D model

    • Run A* and count the explored (purple) cells. Then run Dijkstra on the same maze.
    • Run Greedy best-first. Is its path always the shortest?
    • Press New maze until the goal is hidden behind a long wall, then compare all three algorithms.

    The problem

    A game character must walk to a target; a delivery app must route a driver. We want the shortest path from a start to a goal, and we want to find it fast.

    Dijkstra’s algorithm finds the shortest path but explores in every direction like ripples — even directly away from the goal. A* adds a sense of direction.

    f = g + h

    For every cell n, A* computes:

    • g(n) — the exact cost of the best path found so far from the start to n.
    • h(n) — a heuristic: an estimate of the cost from n to the goal.
    • f(n) = g(n) + h(n) — the estimated total length of a path through n.

    It always expands the cell with the smallest f. Cells that move towards the goal get small f values and are explored first.

    The algorithm

    open   ← {start}        (cells waiting to be explored)
    closed ← {}             (cells already explored)
    g[start] ← 0
    while open is not empty:
        current ← the cell in open with the smallest f
        if current is the goal: follow the parent pointers back → path
        move current from open to closed
        for each neighbour n of current:
            if n is a wall or in closed: skip
            if g[current] + cost < g[n]:
                g[n] ← g[current] + cost
                parent[n] ← current
                add n to open

    In the 3D model: cyan cells are in the open set, purple cells are closed (explored), the yellow raised cell is the current one, and the final path is orange.

    Heuristics

    On a grid where you can move up, down, left and right, the classic heuristic is the Manhattan distance:

    h(n) = |n.row − goal.row| + |n.col − goal.col|

    A heuristic is admissible if it never overestimates the true remaining cost. With an admissible heuristic, A* is guaranteed to return a shortest path. Manhattan distance is admissible here because walls can only make the real path longer.

    Moves allowed Good heuristic
    4 directions Manhattan distance
    8 directions (diagonals) Chebyshev / octile distance
    Any direction Euclidean (straight-line) distance

    A* vs Dijkstra vs greedy

    Algorithm Expands smallest… Shortest path? Speed
    Dijkstra g ✅ Explores the most
    Greedy best-first h ❌ not guaranteed Fast, but can be fooled
    A* g + h ✅ (admissible h) Focused — usually the best balance

    Try all three in the model on the same maze.

    Code

    import heapq
    
    def a_star(grid, start, goal):
        """grid: list of strings, '#' = wall. start/goal: (row, col)."""
        rows, cols = len(grid), len(grid[0])
        h = lambda p: abs(p[0] - goal[0]) + abs(p[1] - goal[1])
        g = {start: 0}
        parent = {}
        open_heap = [(h(start), 0, start)]
        closed = set()
        while open_heap:
            f, cost, cur = heapq.heappop(open_heap)
            if cur in closed:
                continue
            if cur == goal:
                path = [cur]
                while cur in parent:
                    cur = parent[cur]
                    path.append(cur)
                return path[::-1]
            closed.add(cur)
            r, c = cur
            for nr, nc in ((r-1, c), (r+1, c), (r, c-1), (r, c+1)):
                if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != '#':
                    ng = cost + 1
                    if ng < g.get((nr, nc), float('inf')):
                        g[(nr, nc)] = ng
                        parent[(nr, nc)] = cur
                        heapq.heappush(open_heap, (ng + h((nr, nc)), ng, (nr, nc)))
        return None
    
    maze = ["S..#....",
            ".#.#.##.",
            ".#...#..",
            ".####.#.",
            "......#G"]
    print(a_star(maze, (0, 0), (4, 7)))

    Where is A* used?

    Video games (NPC movement), robot navigation, GPS routing (with extra speed-ups), puzzle solving (8-puzzle, Rubik’s cube with pattern-database heuristics) and AI planning.

    Common mistakes

    • Using a heuristic that overestimates (e.g. Euclidean distance × 2) — faster, but no longer guaranteed optimal.
    • Forgetting the closed set, so cells are expanded again and again.
    • Using Manhattan distance when diagonal moves are allowed (it then overestimates).

    Complexity at a glance

    Case / operationTimeWhy
    Worst case (grid with V cells)O(V log V)With a priority queue for the open set.
    With a perfect heuristicO(path length)It walks straight to the goal.
    Extra spaceO(V)

    Quick check

    Test yourself — pick an answer to see if you got it.

    1. In A*, f(n) = g(n) + h(n). What is g(n)?

    2. What does it mean for a heuristic to be admissible?

    3. If h(n) = 0 for every node, A* behaves like…

    4. Greedy best-first search (f = h only) is…

    Saved only in this browser — no account needed.
    Spotted a mistake or a bug in the 3D model?

    Report a mistake

    in A* Search. Thank you — every report makes the lesson better for the next reader.

    We'll also include a link to the step of the 3D model you're on and your browser type, so we can reproduce it.