BFS Breadth First Search on Graph in DSA Go - Time & Space Complexity
We want to understand how the time needed to explore a graph grows as the graph gets bigger using BFS.
Specifically, how does BFS's work change when the graph has more nodes and edges?
Analyze the time complexity of the following code snippet.
func BFS(graph map[int][]int, start int) []int {
visited := make(map[int]bool)
queue := []int{start}
visited[start] = true
order := []int{}
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
order = append(order, node)
for _, neighbor := range graph[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
return order
}
This code visits all nodes reachable from the start node in a graph using BFS.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Visiting each node and checking its neighbors.
- How many times: Each node is visited once; each edge is checked once.
As the number of nodes (n) and edges (e) grow, BFS visits each node once and checks all edges once.
| Input Size (n, e) | Approx. Operations |
|---|---|
| 10 nodes, 15 edges | About 10 node visits + 15 edge checks = 25 operations |
| 100 nodes, 200 edges | About 100 + 200 = 300 operations |
| 1000 nodes, 5000 edges | About 1000 + 5000 = 6000 operations |
Pattern observation: Operations grow roughly in proportion to nodes plus edges.
Time Complexity: O(n + e)
This means BFS takes time proportional to the number of nodes plus the number of edges in the graph.
[X] Wrong: "BFS always takes O(n²) time because it uses nested loops."
[OK] Correct: BFS visits each node and edge only once, so it does not repeatedly check all pairs; the loops do not multiply to n squared.
Understanding BFS time complexity helps you explain how graph traversal scales and why BFS is efficient for many problems.
"What if the graph is represented as an adjacency matrix instead of adjacency lists? How would the time complexity change?"
Practice
Solution
Step 1: Understand BFS traversal
BFS explores nodes by visiting all neighbors of a node before moving to the next level.Step 2: Compare BFS with other traversals
Unlike DFS, BFS uses a queue to explore nodes level by level, which helps in shortest path problems.Final Answer:
To explore all nodes level by level starting from a source node -> Option CQuick Check:
BFS = Level-wise exploration [OK]
- Confusing BFS with DFS
- Thinking BFS uses recursion
- Assuming BFS sorts nodes
Solution
Step 1: Identify data structure for BFS queue
BFS requires a FIFO structure; a slice can be used as a queue in Go.Step 2: Check each option
queue := []int{} // empty slice as queue uses a slice which can enqueue and dequeue elements. queue := make(map[int]bool) // map as queue is a map, not suitable as queue. queue := 0 // integer as queue is an int, not a queue. queue := make(chan int) // channel as queue is a channel, which can be used but requires goroutines and is complex.Final Answer:
queue := []int{} // empty slice as queue -> Option AQuick Check:
Slice as queue = queue := []int{} // empty slice as queue [OK]
- Using map instead of slice for queue
- Using int variable as queue
- Confusing channels with simple queue
Graph edges:
0 -> 1, 2
1 -> 2
2 -> 0, 3
3 -> 3
package main
import "fmt"
func bfs(graph map[int][]int, start int) []int {
visited := make(map[int]bool)
queue := []int{start}
order := []int{}
visited[start] = true
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
order = append(order, node)
for _, neighbor := range graph[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
return order
}
func main() {
graph := map[int][]int{
0: {1, 2},
1: {2},
2: {0, 3},
3: {3},
}
fmt.Println(bfs(graph, 2))
}Solution
Step 1: Start BFS from node 2
Initialize queue=[2], visited={2}. Dequeue 2, enqueue neighbors 0 and 3. order=[2]Step 2: Process nodes in queue
Queue=[0,3]. Dequeue 0, enqueue neighbor 1 (2 already visited). order=[2,0]. Queue=[3,1]. Dequeue 3, neighbor 3 already visited. order=[2,0,3]. Queue=[1]. Dequeue 1, neighbor 2 visited. order=[2,0,3,1]. Queue empty.Final Answer:
[2, 0, 3, 1] -> Option BQuick Check:
BFS order from 2 = [2,0,3,1] [OK]
- Not marking visited before enqueue
- Wrong order of neighbors enqueued
- Confusing BFS with DFS order
func bfs(graph map[int][]int, start int) []int {
visited := make(map[int]bool)
queue := []int{start}
order := []int{}
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
order = append(order, node)
for _, neighbor := range graph[node] {
if !visited[neighbor] {
queue = append(queue, neighbor)
visited[neighbor] = true
}
}
}
return order
}Solution
Step 1: Check visited marking for start node
The code never marks the start node as visited before starting, so it may revisit it.Step 2: Fix by marking start visited before loop
Adding visited[start] = true before the loop prevents revisiting the start node and infinite loops.Final Answer:
Mark start node as visited before loop -> Option AQuick Check:
Start visited before loop = Mark start node as visited before loop [OK]
- Marking visited after enqueue
- Using stack instead of queue
- Ignoring visited map
Solution
Step 1: Understand BFS for shortest path
BFS explores nodes level by level, so the first time target is reached is the shortest path.Step 2: Use a distance map to track levels
Maintain a map to record distance from start for each node. When target is dequeued, return its distance.Final Answer:
Use BFS with a distance map to track levels from start, stop when target found -> Option DQuick Check:
BFS + distance map = shortest path [OK]
- Using DFS for shortest path in unweighted graph
- Ignoring visited nodes causing infinite loops
- Enqueuing nodes multiple times unnecessarily
Ready to prove it?
Pass the test and this pattern moves unknown
