Table of Contents
1047 words
5 minutes
Breadth-First Search: BFS Templates and TypeScript in Practice

Overview#

Breadth-First Search (BFS) is a traversal algorithm that expands layer by layer. Starting from the source, it first visits nodes at distance 1, then nodes at distance 2, making it naturally suited for finding the minimum number of steps in an unweighted graph.

Prerequisites

  • Queue: First in, first out, ensuring layer-by-layer processing
  • Graph / grid modeling: Treating states as nodes and single-step transitions as edges
  • visited set: Avoiding duplicate enqueues and infinite loops

Problem Definition#

Given a starting point and state transition rules, search for the target state in order of increasing distance.

ElementDescription
InputStart point, target, neighbor generation function or graph structure
OutputMinimum steps to reach the target, level-order result, or number of connected components
Core structureQueue + visited set
ApplicabilityEvery edge has equal weight, or only the minimum number of transitions matters

Core Principle: Step-by-Step Illustration#

BFS expands outward layer by layer like ripples in water:

Start step=0 First layer step=1 First layer step=1 Second layer step=2 Second layer step=2 Second layer step=2

Level-Order Expansion#

  1. Enqueue the starting point and mark it as visited.
  2. Each round processes only the nodes currently in the queue, which belong to the same layer.
  3. Add unvisited neighbors to the queue as the next layer.
  4. After processing one layer, increment the step count step + 1.

As soon as the target is encountered for the first time, the current step count can be returned, because BFS guarantees this is the shortest path.


Detailed Algorithm Steps#

Algorithm: BFS(start, target)
Input: start point start, target target
Output: minimum number of steps; return -1 if unreachable
1. queue ← [start]
2. visited ← {start}
3. step ← 0
4. while queue is not empty:
5. size ← queue.length
6. repeat size times:
7. cur ← queue dequeue
8. if cur is target, return step
9. iterate over cur's neighbors next:
10. if next is unvisited: mark and enqueue
11. step ← step + 1
12. return -1

Complexity Analysis:

ScenarioTime ComplexitySpace ComplexityDescription
Graph traversalO(V + E)O(V)Each vertex and edge is processed at most once
Grid BFSO(mn)O(mn)Each cell is enqueued at most once
State searchO(number of states × branching factor)O(number of states)Such as combination lock, word ladder
Bidirectional BFSApproximately O(b^(d/2))O(b^(d/2))Suitable when both start and end are known

TypeScript Implementation#

1. Generic BFS Template#

function bfs<T>(start: T, isTarget: (node: T) => boolean, getNeighbors: (node: T) => T[]): number {
const queue: T[] = [start];
const visited = new Set<T>([start]);
let head = 0;
let step = 0;
while (head < queue.length) {
const size = queue.length - head;
for (let i = 0; i < size; i++) {
const cur = queue[head++];
if (isTarget(cur)) return step;
for (const next of getNeighbors(cur)) {
if (visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
step++;
}
return -1;
}

2. Number of Islands#

function numIslands(grid: string[][]): number {
const m = grid.length;
const n = grid[0].length;
const dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]];
let count = 0;
function floodFill(row: number, col: number): void {
const queue: [number, number][] = [[row, col]];
let head = 0;
grid[row][col] = '0';
while (head < queue.length) {
const [x, y] = queue[head++];
for (const [dx, dy] of dirs) {
const nx = x + dx;
const ny = y + dy;
if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] !== '1') continue;
grid[nx][ny] = '0';
queue.push([nx, ny]);
}
}
}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] === '1') {
count++;
floodFill(i, j);
}
}
}
return count;
}

3. Open the Lock#

function openLock(deadends: string[], target: string): number {
const dead = new Set(deadends);
if (dead.has('0000')) return -1;
const queue = ['0000'];
const visited = new Set<string>(['0000']);
let head = 0;
let step = 0;
function nextStates(state: string): string[] {
const result: string[] = [];
for (let i = 0; i < 4; i++) {
const digit = Number(state[i]);
for (const next of [(digit + 1) % 10, (digit + 9) % 10]) {
result.push(state.slice(0, i) + next + state.slice(i + 1));
}
}
return result;
}
while (head < queue.length) {
const size = queue.length - head;
for (let i = 0; i < size; i++) {
const cur = queue[head++];
if (cur === target) return step;
for (const next of nextStates(cur)) {
if (dead.has(next) || visited.has(next)) continue;
visited.add(next);
queue.push(next);
}
}
step++;
}
return -1;
}

4. Bidirectional BFS#

function openLockBidirectional(deadends: string[], target: string): number {
const dead = new Set(deadends);
if (dead.has('0000')) return -1;
let begin = new Set<string>(['0000']);
let end = new Set<string>([target]);
const visited = new Set<string>(['0000']);
let step = 0;
function nextStates(state: string): string[] {
const result: string[] = [];
for (let i = 0; i < 4; i++) {
const digit = Number(state[i]);
result.push(state.slice(0, i) + ((digit + 1) % 10) + state.slice(i + 1));
result.push(state.slice(0, i) + ((digit + 9) % 10) + state.slice(i + 1));
}
return result;
}
while (begin.size > 0 && end.size > 0) {
if (begin.size > end.size) [begin, end] = [end, begin];
const nextLayer = new Set<string>();
for (const cur of begin) {
if (dead.has(cur)) continue;
if (end.has(cur)) return step;
for (const next of nextStates(cur)) {
if (visited.has(next)) continue;
visited.add(next);
nextLayer.add(next);
}
}
begin = nextLayer;
step++;
}
return -1;
}

Engineering Optimization: Queues and Access Timing#

JavaScript array shift() shifts subsequent elements; for large queue scenarios, use a head pointer to simulate dequeuing.

Optimization PointRecommended PracticeReason
Dequeueconst cur = queue[head++]Avoid the O(n) shifting of shift()
Marking visitsMark when enqueuingPrevent the same node from being enqueued repeatedly
Level countingRecord the current level sizeAccurately maintain the shortest step count
Bidirectional searchExpand from the smaller frontierReduce the exponential state space

Applications and Limitations#

Typical Applications#

  • Shortest path in unweighted graphs
  • Level-order traversal of binary trees
  • Grid propagation: islands, mazes, rotting oranges
  • State search: combination lock, word ladder

Limitations#

LimitationDescription
High memory usageThe queue may hold an entire level of nodes
Not suitable for weighted shortest pathsWhen edge weights differ, algorithms such as Dijkstra should be used
State explosionWhen the branching factor is large, pruning or bidirectional search is needed

Summary#

Enqueue start Process current layer Find unvisited neighbors Enqueue neighbors Increment step count

Key Points:

  1. BFS = queue + visited + level-by-level expansion.
  2. In an unweighted graph, the first time the target is reached is the shortest path.
  3. For large queues, use a head pointer; do not frequently shift().
  4. When both the start and end points are known, prioritize bidirectional BFS.
Breadth-First Search: BFS Templates and TypeScript in Practice
https://www.hehonglei.com/technology/bfs/
Author
HHLBlog
Published at
2026-04-26
License
CC BY-NC-SA 4.0