How it works
Breadth-First Search explores the grid in expanding rings: it visits every neighbour of the start, then every neighbour of those, and so on, using a FIFO queue. The first time it reaches the goal it has found a shortest path in terms of number of steps.
Because it expands uniformly in all directions, BFS guarantees the fewest-edges path on an unweighted grid.
Implementation
function bfsPathfind(grid, start, end, allowDiag) { const visited = new Set([key(start[0], start[1])]); const prev = new Map(); const queue = [[start[0], start[1]]]; while (queue.length) { const __pattern1 = queue.shift(); const cr = __pattern1[0]; const cc = __pattern1[1]; visitNode(cr, cc); if (cr === end[0] && cc === end[1]) { reconstructPath(prev, start, end); return; } for (const [nr, nc] of getNeighbors(grid, cr, cc, allowDiag)) { const neighborKey = key(nr, nc); if (visited.has(neighborKey)) continue; visited.add(neighborKey); prev.set(neighborKey, [cr, cc]); queue.push([nr, nc]); pushFrontier(nr, nc); } } reportNoPath(); }
def bfsPathfind(grid, start, end, allowDiag): visited = Set([key(start[0], start[1])]) prev = Map() queue = [[start[0], start[1]]] while queue.length: __pattern1 = queue.shift() cr = __pattern1[0] cc = __pattern1[1] visitNode(cr, cc) if ((cr == end[0]) and (cc == end[1])): reconstructPath(prev, start, end) return for nr, nc in getNeighbors(grid, cr, cc, allowDiag): neighborKey = key(nr, nc) if visited.has(neighborKey): continue visited.add(neighborKey) prev.set(neighborKey, [cr, cc]) queue.push([nr, nc]) pushFrontier(nr, nc) reportNoPath()
#include <vector> #include <algorithm> void bfsPathfind(int grid, int start, int end, int allowDiag) { auto visited = Set([key(start[0], start[1])]); auto prev = Map(); auto queue = [[start[0], start[1]]]; while(queue.length) { auto __pattern1 = queue.shift(); auto cr = __pattern1[0]; auto cc = __pattern1[1]; visitNode(cr, cc); if(((cr == end[0]) && (cc == end[1]))) { reconstructPath(prev, start, end); return; } for(auto& [nr, nc] : getNeighbors(grid, cr, cc, allowDiag)) { auto neighborKey = key(nr, nc); if(visited.has(neighborKey)) { continue; } visited.add(neighborKey); prev.set(neighborKey, [cr, cc]); queue.push([nr, nc]); pushFrontier(nr, nc); } } reportNoPath(); }
public void bfsPathfind(int grid, int start, int end, int allowDiag) { var visited = Set([key(start[0], start[1])]); var prev = Map(); var queue = [[start[0], start[1]]]; while(queue.length) { var __pattern1 = queue.shift(); var cr = __pattern1[0]; var cc = __pattern1[1]; visitNode(cr, cc); if(((cr == end[0]) && (cc == end[1]))) { reconstructPath(prev, start, end); return; } foreach(var (nr, nc) in getNeighbors(grid, cr, cc, allowDiag)) { var neighborKey = key(nr, nc); if(visited.has(neighborKey)) { continue; } visited.add(neighborKey); prev.set(neighborKey, [cr, cc]); queue.push([nr, nc]); pushFrontier(nr, nc); } } reportNoPath(); }
#include <stdio.h> void bfsPathfind(int grid, int start, int end, int allowDiag) { var visited = Set([key(start[0], start[1])]); var prev = Map(); var queue = [[start[0], start[1]]]; while(queue.length) { var __pattern1 = queue.shift(); var cr = __pattern1[0]; var cc = __pattern1[1]; visitNode(cr, cc); if(((cr == end[0]) && (cc == end[1]))) { reconstructPath(prev, start, end); return; } for(int _fod_i = 0; _fod_i < getNeighbors(grid, cr, cc, allowDiag)_len; _fod_i++) { int nr = getNeighbors(grid, cr, cc, allowDiag)[_fod_i][0]; int nc = getNeighbors(grid, cr, cc, allowDiag)[_fod_i][1]; var neighborKey = key(nr, nc); if(visited.has(neighborKey)) { continue; } visited.add(neighborKey); prev.set(neighborKey, [cr, cc]); queue.push([nr, nc]); pushFrontier(nr, nc); } } reportNoPath(); }
Advantages
- Guarantees the shortest path on unweighted grids.
- Simple, predictable, and complete — it always finds a path if one exists.
Disadvantages
- Ignores edge weights, so it is not suitable for weighted terrain.
- Explores many cells; A* is far more focused when a heuristic is available.
When to use it
Use BFS for shortest paths on uniform-cost grids, flood fills, and reachability checks.