Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 23 – Weighted graphs: shortest paths and spanning trees

A* search with admissible heuristics

A warehouse robot's fastest route over slow and blocked floor tiles: the A* search problem, its Dijkstra baseline and the signal for an estimate.

  • Advanced
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 8 of 35 minutes free
Free: The problem: a robot on a warehouse floor · The brute force, and why it fails at 5,000 trips a minute · What to notice. With a pass: the other 14 sections and 3 interview answers, about 27 minutes.

What you will learn

  • Find the cheapest route on a grid with A*, ordering cells by time so far plus the Manhattan distance to the goal
  • Explain admissibility and consistency, and prove what each one guarantees
  • Measure how many cells A* settles against Dijkstra, and how the tie rule changes that count
  • Show with a run that an estimate which overestimates can return a slower route
  • Choose estimates for diagonal moves and for several goals

Before you start

What you’ll learn · Before you start

What you will learn

  • Find the cheapest route on a grid with A*, ordering cells by time so far plus the Manhattan distance to the goal
  • Explain admissibility and consistency, and prove what each one guarantees
  • Measure how many cells A* settles against Dijkstra, and how the tie rule changes that count
  • Show with a run that an estimate which overestimates can return a slower route
  • Choose estimates for diagonal moves and for several goals

Before you start

On this page

A* search finds a cheapest route from a start to one known goal, as Dijkstra’s algorithm does, but it orders its queue by the cost so far plus an estimate of the cost still to come. With an admissible heuristic, an estimate that never overestimates the remaining cost, the moment A* takes the goal from its queue it has a cheapest route. On grids the usual estimate is the Manhattan distance to the goal. A good estimate steers the search towards the goal, so it settles fewer cells than Dijkstra for the same answer.

The problem: a robot on a warehouse floor

A warehouse floor is a grid of up to 300 × 300 cells. # is a shelf the robot cannot enter, . a tile that takes 1 second to enter and ~ a wet tile that takes 3. The robot at S moves one cell up, down, left or right per step, and entering S or the packing desk T takes 1 second. Return the fewest seconds from S to T, or −1 if T cannot be reached. The control computer plans 5,000 trips a minute, so a trip should settle as few cells as it can.

  • The 6 × 9 floor in the diagram takes 15 seconds; its route crosses one wet tile because going round costs more.
  • S.#T above ..#. cannot be done: −1.
  • S~T takes 4 seconds: 3 for the wet tile and 1 for the desk.
A 6 by 9 warehouse floor with shelves, wet tiles, each cell's Manhattan distance to the desk, and the 15-second route.The small floor: 15 seconds to the deskS12119876512108~7~65411987~63106~5432987531~8~76542TSTa shelf~a wet tile, 3 secondsthe fastest route, 15 secondssmall number: Manhattan distance to the desk

The first example, with the Manhattan distance in every cell

Text description of the diagram

A grid of 6 rows and 9 columns. The robot S is in the top-left corner and the packing desk T in the bottom-right corner. Dark cells are shelves: in row 0 at column 3; in row 1 at columns 1 and 3; in row 2 at columns 1, 6 and 7; in row 3 at columns 1, 2 and 3; in row 4 at columns 3, 5 and 7; in row 5 at columns 5 and 7. Light blue cells marked with a tilde are wet tiles, which take 3 seconds to enter: row 1 at columns 5 and 6, row 2 at column 5, row 3 at column 5, and row 5 at columns 0 and 1. Every other cell takes 1 second.

Each open cell shows a small number, its Manhattan distance to the desk: 12 next to the robot, falling to 1 next to the desk. A thick line marks the fastest route, 15 seconds: right along row 0 to column 2, down column 2 to row 2, right to column 4, down to row 3, right along row 3 through the wet tile at column 5 to column 8, and down to the desk. The route spends 3 seconds on one wet tile, because going around it costs more.

The brute force, and why it fails at 5,000 trips a minute

Listing routes is hopeless, and Dijkstra’s algorithm, though right, searches nearly the whole floor:

Counting routes, and Dijkstra on the small floor and on a 300 × 300 floor Python · brute/brute_force.py
"""Two baselines for the robot's route: listing routes, which explodes, and Dijkstra's algorithm, which is right but
settles almost every cell that the robot could reach sooner than the desk."""

import heapq
import time
from math import comb

COST = {".": 1, "~": 3, "S": 1, "T": 1}


def dijkstra(floor):
    """(seconds to the desk, cells settled), or (-1, cells settled) when the desk cannot be reached."""
    rows, cols = len(floor), len(floor[0])
    start = next((r, row.index("S")) for r, row in enumerate(floor) if "S" in row)
    desk = next((r, row.index("T")) for r, row in enumerate(floor) if "T" in row)
    best, heap, settled = {start: 0}, [(0, start)], 0
    while heap:
        g, (r, c) = heapq.heappop(heap)
        if g > best[(r, c)]:
            continue
        settled += 1
        if (r, c) == desk:
            return g, settled
        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 floor[nr][nc] != "#":
                ng = g + COST[floor[nr][nc]]
                if ng < best.get((nr, nc), float("inf")):
                    best[(nr, nc)] = ng
                    heapq.heappush(heap, (ng, (nr, nc)))
    return -1, settled


def floor_of(rows, cols, seed, shelves=20, wet=15):
    state, out = seed, []
    for _ in range(rows):
        row = []
        for _ in range(cols):
            state = state * 48271 % 2147483647
            v = state % 100
            row.append("#" if v < shelves else "~" if v < shelves + wet else ".")
        out.append(row)
    out[0][0], out[-1][-1] = "S", "T"
    return ["".join(row) for row in out]


def open_cells(floor):
    return sum(row.count(".") + row.count("~") + row.count("S") + row.count("T") for row in floor)


SMALL = ["S..#.....",
         ".#.#.~~..",
         ".#...~##.",
         ".###.~...",
         "...#.#.#.",
         "~~...#.#T"]
for name, floor in [("small floor", SMALL), ("walled-off desk", ["S.#T", "..#."]), ("one wet tile", ["S~T"])]:
    seconds, settled = dijkstra(floor)
    print(f"{name}: {seconds} s; Dijkstra settled {settled} of {open_cells(floor)} open cells")

print(f"routes of 38 steps across an open 20 x 20 floor, corner to corner: {comb(38, 19):,}")

big = floor_of(300, 300, 1)
start = time.process_time()
seconds, settled = dijkstra(big)
used = time.process_time() - start
print(f"300 x 300 floor: {seconds} s; Dijkstra settled {settled:,} of {open_cells(big):,} open cells in {used:.2f} s of CPU")
print(f"at that speed, 5,000 trips a minute need {5000 * used / 60:.0f} CPU cores")

Output

small floor: 15 s; Dijkstra settled 40 of 40 open cells
walled-off desk: -1 s; Dijkstra settled 4 of 6 open cells
one wet tile: 4 s; Dijkstra settled 3 of 3 open cells
routes of 38 steps across an open 20 x 20 floor, corner to corner: 35,345,263,800
300 x 300 floor: 620 s; Dijkstra settled 71,927 of 72,081 open cells in 0.13 s of CPU
at that speed, 5,000 trips a minute need 11 CPU cores

This output changes from run to run: the CPU time depends on the computer and on what else it is doing; the seconds and the counts do not

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

An open 20 × 20 floor alone has 35,345,263,800 shortest routes from corner to corner. Dijkstra settles all 40 open cells of the small floor, and 71,927 of the 72,081 open cells of the 300 × 300 floor, because the desk is in the far corner. In the recorded run a single trip took over a tenth of a second of CPU in CPython, so 5,000 trips a minute would need more than ten processor cores.

What to notice

  • The desk is known. Dijkstra spreads out evenly in every direction and never uses where the desk is.
  • A cheap lower bound exists. Every step takes at least 1 second, so the time still needed is at least the Manhattan distance to the desk.
  • Bounds add up. Time so far plus that bound is a lower bound for every route through a cell, so cells can be ordered by it.

Five ideas follow:

  • A* orders cells by g + h, the seconds so far plus the Manhattan distance to the desk.
  • An admissible estimate never exceeds the true remaining time.
  • A consistent estimate drops by at most the cost of each step, so a step never lowers g + h.
  • A tie rule decides which of two cells with equal g + h comes out of the queue first.
  • A weighted estimate multiplies h by a number above 1 and gives up the guarantee of a fastest route.

A* step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Why an admissible estimate returns the fastest route, proved
  • Why a consistent estimate settles each cell once, proved
  • A dry run on the small floor
  • Dijkstra against A* on 300 × 300 floors, measured
  • The code in five languages
  • How much work it does
  • Edge cases and common bugs
  • Variants: diagonal moves and several desks
  • Follow-up questions
  • Exercise: the robot’s fastest route, within a budget of cells
    • The robot's fastest route, within a budget of cells
  • A practice set with sample answers
  • Check yourself
  • Interview questions
    • Why is the Manhattan distance an admissible estimate on a grid with four moves where every step costs at least 1, and when does it stop being one?
    • A game moves units on a 512 × 512 tile map in eight directions, and a diagonal step costs 1.4. A teammate's A* with the Manhattan estimate sometimes returns longer paths than a slow Dijkstra. Explain why, fix it, and say what the fix costs in speed.
    • Your A* with the Manhattan estimate gives right answers, but on large open maps it settles almost every cell, as Dijkstra does. Why, and what small change fixes it? Show that the change keeps every answer right.
    • A delivery app answers 1,000 route queries a second on a fixed road network of 2 million junctions with travel times as costs. The straight-line estimate is weak because roads wind. How would you make A* fast, and what would you precompute?
  • Key takeaways

1 diagram · 6 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 25 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; nothing renews unless you choose to. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.