Breadth-First Search solves the Graph Traversal problem. Although the algorithm can work for both directed and undirected graphs, we will focus our attention on undirected graphs, leaving it to the reader to determine what changes when the graph is directed.
Breadth-First Search (BFS) is a fundamental graph traversal algorithm whose goal is to explore every vertex reachable from a given source vertex in the most systematic way possible. Specifically, it discovers each reachable node and determines a shortest path to it—measured in the fewest number of edges—in an unweighted graph. BFS is especially useful when we want to answer questions like: “Which vertices can be reached from here?” or “What is the shortest path to each one?”
The key idea behind BFS is to explore the graph in layers. It begins at the source and visits all neighboring vertices (distance 1), then all vertices that are two edges away (distance 2), and so on. This ensures that the first time a vertex is discovered, it is along a path with the fewest possible edges from the source.
To manage this layer-by-layer process, BFS uses a queue to hold vertices that have been discovered but not yet fully explored. At each step, the algorithm dequeues a vertex, examines its neighbors, and enqueues any that haven’t been visited yet. To prevent revisiting the same vertex (which is especially important in graphs with cycles), BFS marks each vertex as visited the moment it is enqueued.
As a result, BFS explores every node reachable from the source exactly once, and computes the shortest-path distance from the source to each of them. If desired, it can also be extended to record the full path taken to reach each vertex.
On the graph below, a BFS from source \( A \) visits nodes in the order: \( A \) (distance 0), then \( B \), \( C \) (distance 1), and finally \( D \), \( E \) (distance 2).
In this graph, BFS from \( A \) visits \( A \) (distance 0), then \( B \) and \( C \) (distance 1), followed by \( D \) and \( E \) (distance 2). When visiting \( B \), the algorithm examines neighbors \( A \), \( C \), and \( E \). Since \( A \) and \( C \) have already been discovered, only \( E \) is enqueued. Similarly, when visiting \( C \), it checks \( A \), \( B \), and \( D \), and enqueues only \( D \). This behavior—skipping neighbors that were already visited—ensures that each vertex is discovered exactly once and at the earliest possible moment, which guarantees that its recorded distance from the source is correct.
In this graph, a BFS starting at \( A \) explores only the left component: \( A \) (distance 0), \( B \), \( C \) (distance 1), \( D \), \( E \) (distance 2). Nodes \( F \) and \( G \) are not reachable from \( A \), so they are not discovered.
Breadth-First Search not only explores all vertices reachable from a source vertex \( s \), but also computes the shortest distance (in number of edges) from \( s \) to every other reachable vertex.
To support this, the algorithm maintains a dist array (or map), where dist[v] records the number of edges from \( s \) to vertex \( v \).
Optionally, BFS can also maintain a pred array (or map), where pred[v] stores the predecessor of \( v \) on the shortest path from \( s \), which allows reconstructing the full path later.
Given a source vertex s, BFS proceeds as follows:
dist[] and pred[] with default values.s as visited, set dist[s] = 0, pred[s] = null, and enqueue s.u.v of u:
v is unvisited:
v as visited.dist[v] = dist[u] + 1.pred[v] = u.v.
This procedure visits vertices in increasing order of their distance from s.
The interactive demo below illustrates BFS on a sample graph, showing the layer-by-layer exploration.
In the demo, vertex color indicates visit status: blue nodes are unvisited, orange nodes have been discovered but not fully processed, and green nodes have had all their neighbors explored
(traditionally the colors used are white, gray, and black).
While the pseudocode uses a simple boolean visited array, implementations like the demo may use multiple states to track the progress of each vertex more precisely.
This three-color model (unvisited, discovered, processed) is not only helpful for visualization — it plays an essential role in many algorithms built on top of BFS.
For example, detecting whether a graph is bipartite involves assigning alternating colors to layers and checking for cross-layer conflicts,
and certain cycle detection algorithms rely on distinguishing whether a neighbor has been fully processed or is still in the queue.
While these ideas can be implemented using additional logic with a boolean visited array, the color/state distinction provides a more expressive and modular framework for reasoning about vertex status during traversal.
To implement BFS effectively, you need several supporting data structures:
a visited[] array (or equivalent state labels) to track which vertices have been discovered,
a queue to manage the frontier of the search,
a dist[] array to record the shortest distance from the source to each vertex,
and optionally a pred[] array to store the predecessor of each vertex on its shortest path.
The dist array is updated as soon as a vertex is first discovered, guaranteeing that it reflects the correct shortest-path length,
and the pred array can be used after traversal to reconstruct the shortest path by walking backward from any vertex to the source.
Each implementation below uses idiomatic data structures for its language:
List[] for the adjacency list, Queue (typically LinkedList) for the BFS queue, and arrays for visited and dist.vector<vector<int>> for the adjacency list, queue<int> from the STL for the BFS queue, and vectors for visited and dist.deque from the collections module for the queue, and lists for visited and dist.These implementations do not include predecessor tracking or multi-state coloring, since the basic version of BFS does not require them for computing distances alone.
void bfs(List<Integer>[] graph, int s, int[] dist) {
int n = graph.length;
boolean[] visited = new boolean[n];
Arrays.fill(dist, Integer.MAX_VALUE);
Queue<Integer> q = new LinkedList<>();
visited[s] = true;
dist[s] = 0;
q.add(s);
while (!q.isEmpty()) {
int u = q.poll();
for (int v : graph[u]) {
if (!visited[v]) {
visited[v] = true;
dist[v] = dist[u] + 1;
q.add(v);
}
}
}
}
void bfs(const vector<vector<int>>& graph, int s, vector<int>& dist) {
int n = graph.size();
vector<bool> visited(n, false);
dist.assign(n, INT_MAX);
queue<int> q;
visited[s] = true;
dist[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : graph[u]) {
if (!visited[v]) {
visited[v] = true;
dist[v] = dist[u] + 1;
q.push(v);
}
}
}
}
def bfs(graph, s):
n = len(graph)
visited = [False] * n
dist = [float('inf')] * n
q = deque()
visited[s] = True
dist[s] = 0
q.append(s)
while q:
u = q.popleft()
for v in graph[u]:
if not visited[v]:
visited[v] = True
dist[v] = dist[u] + 1
q.append(v)
return dist
Time Complexity:
Breadth-First Search runs in \( O(V + E) \) time when using an adjacency list representation.
The outer while loop runs as long as the queue is nonempty, and since each vertex is enqueued at most once, it executes at most \( V \) times.
However, we cannot simply multiply this by the size of the inner for loop to get the total work—doing so would overcount, leading to an overestimate of \( O(V \cdot d) \), where \( d \) is the maximum degree.
Instead, we analyze more carefully:
for loop is executed once per vertex, and iterates over all neighbors of that vertex.
Since each edge appears in the adjacency list of both its endpoints, the total number of neighbor visits is at most \( 2E \) in an undirected graph.for loop performs a constant amount of work—whether or not the neighbor has been visited—including checking a condition, possibly marking a vertex visited, updating data, and enqueuing it.Thus, the total time is proportional to the number of vertices plus the number of adjacency-list entries: \( O(V + E) \).
Space Complexity: BFS uses \( O(V) \) additional space beyond the graph itself:
visited[]: \( O(V) \)dist[]: \( O(V) \)pred[]: \( O(V) \)The graph is stored as an adjacency list, which occupies \( O(V + E) \) space and is considered part of the input. So the total extra space used by BFS is \( O(V) \).
Once BFS has completed, we can classify each edge in the graph based on how it relates to the traversal tree that was constructed:
pred[v] = u. These are the edges that were used to discover new vertices during the search, and form the actual BFS tree.v is not a child of u in the BFS tree. In undirected graphs, these usually connect siblings or cousins in different layers.
These classifications are useful in applications such as detecting cycles, checking graph structure, or understanding redundancy in connections.
In BFS, edges are examined when their endpoints are discovered during traversal, so classifications can be determined from the visited and pred arrays after the search.
Note that edges in components not reachable from the source will not be explored at all.
pred[] array to record each vertex's predecessor, you can reconstruct shortest paths from the source to any reachable vertex after the search completes.visited flag. For instance, distinguishing between "discovered but not processed" and "fully processed" can help with detecting cycles or checking bipartiteness. Notice that although the demo used these three colors, the pseudocode we gave did not.dist[v] and when is it updated?dist[v] stores the shortest-path distance (number of edges) from the source to vertex \( v \), and is set to dist[u] + 1 when \( v \) is first discovered from \( u \).pred[] to Reconstruct a Path:
Run the demo for a graph with 9 or 10 nodes. Then for each vertex, use the
pred[] array (called just p in the demo) to construct a
shortest path from the source to the given vertex. Does each have the correct number
of edges? When you are convinced you understand how it is done,
give code/pseudocode for an algorithm to do this in general.pred[].A.
For each vertex v, record its parent pred[v] in the resulting BFS tree (use None or - for the root).
Submit both your completed pred[] array and a drawing of the BFS tree that shows how the vertices are connected.
A.
Assume adjacency lists are stored in alphabetical order.
For BFS, list the shortest distance to A for each vertex and clearly indicate the tree edges.
For DFS, list the timestamps and clearly indicate the tree edges.
How do the traversal patterns differ, and why?
pred[]:
Use the BFS demo on a graph with 9–10 vertices and observe how the p array (which stores predecessors) is updated during the traversal.
p[] to manually reconstruct the path from the source to that vertex. Write out each path as a sequence of vertices.dist[] array shown in the demo.pred[] array, and a target vertex \( t \), and returns the path from the \(s\) to \( t \) as a list of vertices in order from \(s\) to \(t\).| S | █ | █ | |||||
| █ | █ | █ | █ | ||||
| █ | |||||||
| █ | █ | █ | █ | █ | █ | █ | |
| █ | |||||||
| █ | |||||||
| █ | █ | █ | █ | █ | █ | █ | |
| G |