Imagine a city map with many locations connected by roads:
A
/ \
B C
| |
D---EIf you want to visit every location, you need a systematic way to move through the graph.
This process is called Graph Traversal.
Graph traversal is the process of visiting the vertices of a graph in a systematic way.
The two fundamental graph traversal techniques are:
DFS → Depth-First Search
BFS → Breadth-First SearchBoth can visit all reachable vertices, but they explore the graph differently.
Why Do We Need Graph Traversal?
Unlike an array:
10 → 20 → 30 → 40a graph can have many connections:
0
/ \
1 2
| |
3---4There isn't one simple direction in which to move.
Starting from 0, we could explore:
0 → 1 → 3 → 4 → 2or:
0 → 2 → 4 → 3 → 1depending on the traversal strategy and neighbor order.
Graph traversal gives us a systematic method for exploring these connections.
The Two Main Traversals
There are two fundamental approaches:
Depth-First Search
↓
DFS
Breadth-First Search
↓
BFSThe easiest way to understand the difference is:
DFS → Go deep first
BFS → Go level by levelDFS — Depth-First Search
Depth-First Search explores as far as possible along one path before backtracking.
Consider:
0
/ \
1 2
/ \
3 4Starting from 0, one possible DFS order is:
0 → 1 → 3 → 4 → 2The exact order depends on the order in which neighbors are stored.
The basic idea is:
Start
↓
Visit node
↓
Go to an unvisited neighbor
↓
Keep going deeper
↓
Backtrack when necessaryWhy Is DFS Called "Depth-First"?
Look at:
0
/ \
1 2
/ \
3 4Starting from 0, DFS doesn't immediately process 2.
Instead, it goes:
0
↓
1
↓
3It goes as deep as it can.
When 3 has no unvisited neighbor, it backtracks:
3
↑
1and then explores 4.
That's why it's called Depth-First Search.
DFS and Recursion
DFS is naturally implemented using recursion.
Why?
Because recursion automatically gives us a stack of function calls.
The basic pattern is:
static void dfs(
int node,
ArrayList<ArrayList<Integer>> graph,
boolean[] visited
) {
visited[node] = true;
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
dfs(neighbor, graph, visited);
}
}
}The most important part is:
visited[node] = true;We mark the node as visited before exploring its neighbors.
Why Do We Need visited[]?
This is one of the biggest differences between tree traversal and graph traversal.
Graphs can contain cycles.
Consider:
A ─ B
| |
C ─ DWe can travel:
A → B → D → C → AIf we don't remember which nodes we've already visited, the traversal could continue indefinitely.
So we maintain:
boolean[] visited =
new boolean[vertices];When we visit a node:
visited[node] = true;Then we don't visit it again.
Complete DFS Example
Consider:
0 ─ 1
| |
2 ─ 3Adjacency list:
0 → [1, 2]
1 → [0, 3]
2 → [0, 3]
3 → [1, 2]Java:
import java.util.ArrayList;
class Main {
static void dfs(
int node,
ArrayList<ArrayList<Integer>> graph,
boolean[] visited
) {
visited[node] = true;
System.out.print(node + " ");
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
dfs(
neighbor,
graph,
visited
);
}
}
}
public static void main(String[] args) {
int vertices = 4;
ArrayList<ArrayList<Integer>> graph =
new ArrayList<>();
for (int i = 0; i < vertices; i++) {
graph.add(new ArrayList<>());
}
graph.get(0).add(1);
graph.get(1).add(0);
graph.get(0).add(2);
graph.get(2).add(0);
graph.get(1).add(3);
graph.get(3).add(1);
graph.get(2).add(3);
graph.get(3).add(2);
boolean[] visited =
new boolean[vertices];
dfs(0, graph, visited);
}
}One possible output is:
0 1 3 2Again, the exact DFS order depends on neighbor ordering.
Iterative DFS Using a Stack
DFS doesn't have to use recursion.
We can explicitly use a Stack.
Remember:
Stack → LIFO
Last In, First OutJava implementation:
import java.util.Stack;
static void dfs(
int start,
ArrayList<ArrayList<Integer>> graph
) {
boolean[] visited =
new boolean[graph.size()];
Stack<Integer> stack =
new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
int node = stack.pop();
if (visited[node]) {
continue;
}
visited[node] = true;
System.out.print(node + " ");
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
stack.push(neighbor);
}
}
}
}So:
DFS
↓
Recursion
or
StackBoth approaches implement the same fundamental depth-first idea.
BFS — Breadth-First Search
Now let's look at the other major traversal.
Breadth-First Search explores the graph level by level.
Consider:
0
/ \
1 2
/ \
3 4Starting from 0:
Level 0 → 0
Level 1 → 1, 2
Level 2 → 3, 4So BFS visits:
0 → 1 → 2 → 3 → 4The exact order can depend on the adjacency-list order.
Why Is BFS Called "Breadth-First"?
DFS goes:
0
↓
1
↓
3BFS instead processes the entire current level before going deeper:
0 Level 0
/ \
1 2 Level 1
/ \
3 4 Level 2So:
DFS → Depth first
BFS → Breadth firstWhy Does BFS Use a Queue?
BFS naturally uses a Queue.
A queue follows:
FIFO
First In, First OutSuppose we start at 0.
First:
Queue:
0Remove 0.
Add its neighbors:
Queue:
1, 2Remove 1.
Add its unvisited neighbors:
Queue:
2, 3, 4Then process 2, followed by 3 and 4.
This naturally creates level-by-level traversal.
BFS in Java
import java.util.LinkedList;
import java.util.Queue;
static void bfs(
int start,
ArrayList<ArrayList<Integer>> graph
) {
boolean[] visited =
new boolean[graph.size()];
Queue<Integer> queue =
new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.remove();
System.out.print(node + " ");
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.add(neighbor);
}
}
}
}Notice that we mark the node as visited when we add it to the queue.
This prevents the same node from being added multiple times through different edges.
DFS vs BFS
Let's use:
0
/ \
1 2
/ \
3 4One possible DFS:
0 → 1 → 3 → 4 → 2BFS:
0 → 1 → 2 → 3 → 4The core difference:
DFS | BFS |
|---|---|
Goes deep first | Goes level by level |
Recursion / Stack | Queue |
Uses backtracking | Expands outward |
Useful for components/cycles | Useful for shortest paths in unweighted graphs |
BFS for Shortest Path
One of the most important properties of BFS is that it can find the shortest path in an unweighted graph, where every edge has equal cost.
Consider:
A
/ \
B C
| |
D E
\ /
FSuppose we want the shortest path from A to F.
BFS explores:
Distance 0:
A
Distance 1:
B, C
Distance 2:
D, E
Distance 3:
FSo the shortest distance is:
3 edgesOne shortest path is:
A → B → D → FFor weighted graphs, ordinary BFS is generally not sufficient; algorithms such as Dijkstra's are used when appropriate.
BFS with Distance
We can maintain a distance array.
static void bfs(
int start,
ArrayList<ArrayList<Integer>> graph
) {
int[] distance =
new int[graph.size()];
boolean[] visited =
new boolean[graph.size()];
Queue<Integer> queue =
new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.remove();
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
distance[neighbor] =
distance[node] + 1;
queue.add(neighbor);
}
}
}
}If:
distance[0] = 0then its neighbors get:
distance = 1their neighbors get:
distance = 2and so on.
Finding Connected Components
Suppose the graph is:
0 ─ 1 3 ─ 4
| \
2 5There are two separate groups:
Component 1:
0, 1, 2
Component 2:
3, 4, 5We can use DFS or BFS to count them.
The basic idea is:
For every vertex:
If it is unvisited:
Start DFS/BFS
Increment component countJava:
static int countComponents(
ArrayList<ArrayList<Integer>> graph
) {
int components = 0;
boolean[] visited =
new boolean[graph.size()];
for (int i = 0;
i < graph.size();
i++) {
if (!visited[i]) {
components++;
dfs(i, graph, visited);
}
}
return components;
}This pattern appears frequently in graph problems.
Cycle Detection
Graph traversal is also used to detect cycles.
Consider:
A ─ B
| |
C ─ DThere is a cycle:
A → B → D → C → ADuring traversal, we need to determine whether we've encountered a previously visited vertex in a way that indicates a cycle.
The exact implementation differs between:
Undirected Graphand:
Directed GraphFor undirected graphs, DFS often tracks the parent of the current node.
For directed graphs, DFS often uses an additional recursion-stack or three-state technique.
DFS for Connected Components
DFS can also be used to explore an entire component.
For:
0 ─ 1
| |
2 ─ 3starting from 0:
DFS(0)
↓
1
↓
3
↓
2Once DFS finishes, every node reachable from 0 has been visited.
This is why DFS is useful for:
Connected components
Cycle detection
Path finding
Topological sorting
Backtracking
Graph exploration
DFS and Backtracking
DFS and backtracking are closely related.
Suppose we're exploring:
A
├── B
│ ├── D
│ └── E
└── CDFS goes down one branch:
A → B → DThen when it reaches a dead end, it backtracks:
D → B → EThis same idea appears in:
Maze solving
Permutations
Combinations
Sudoku
N-Queens
Path finding
So when you see a problem involving exploring possibilities and undoing choices, DFS/backtracking is often nearby.
DFS vs BFS in Real Life
Imagine you're looking for a person in a network.
You
├── Friend A
│ ├── Friend D
│ └── Friend E
└── Friend B
└── Friend FDFS
You might follow:
You
↓
Friend A
↓
Friend Dand keep exploring that branch.
BFS
You might first check:
Your direct friendsthen:
Friends of friendsthen:
Friends of friends of friendsThis is why BFS is naturally suited to finding things based on the fewest number of connections.
Graph Traversal with an Adjacency Matrix
Graph traversal can also work with an adjacency matrix.
Suppose:
0 ─ 1
| |
2 ─ 3The matrix tells us whether an edge exists.
During DFS, for every vertex we may need to check:
for (int neighbor = 0;
neighbor < vertices;
neighbor++) {
if (graph[node][neighbor] == 1
&& !visited[neighbor]) {
dfs(
neighbor,
graph,
visited
);
}
}This works, but we scan all possible vertices for each node.
Therefore, with an adjacency matrix:
DFS/BFS → O(V²)For sparse graphs, adjacency lists are usually more efficient.
Traversal Complexity with Adjacency List
With an adjacency list:
DFS → O(V + E)
BFS → O(V + E)Why?
We may visit every vertex:
Vand inspect every edge:
ESo:
O(V + E)This is one of the most important formulas in graph DSA.
Traversal Complexity with Adjacency Matrix
With an adjacency matrix:
DFS → O(V²)
BFS → O(V²)because for each vertex, we may need to scan an entire row of V possible neighbors.
So:
Adjacency List:
O(V + E)
Adjacency Matrix:
O(V²)A Complete Java Example
Let's implement both DFS and BFS for the same graph.
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
class Main {
static void addEdge(
ArrayList<ArrayList<Integer>> graph,
int u,
int v
) {
graph.get(u).add(v);
graph.get(v).add(u);
}
static void dfs(
int node,
ArrayList<ArrayList<Integer>> graph,
boolean[] visited
) {
visited[node] = true;
System.out.print(node + " ");
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
dfs(
neighbor,
graph,
visited
);
}
}
}
static void bfs(
int start,
ArrayList<ArrayList<Integer>> graph
) {
boolean[] visited =
new boolean[graph.size()];
Queue<Integer> queue =
new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.remove();
System.out.print(node + " ");
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.add(neighbor);
}
}
}
}
public static void main(String[] args) {
int vertices = 5;
ArrayList<ArrayList<Integer>> graph =
new ArrayList<>();
for (int i = 0; i < vertices; i++) {
graph.add(new ArrayList<>());
}
addEdge(graph, 0, 1);
addEdge(graph, 0, 2);
addEdge(graph, 1, 3);
addEdge(graph, 2, 4);
addEdge(graph, 3, 4);
System.out.print("DFS: ");
boolean[] visited =
new boolean[vertices];
dfs(0, graph, visited);
System.out.print("\nBFS: ");
bfs(0, graph);
}
}One possible output is:
DFS: 0 1 3 4 2
BFS: 0 1 2 3 4The DFS result can change if the order of neighbors in the adjacency lists changes.
When Should You Use DFS?
DFS is a strong choice when the problem involves:
Exploring an entire branch
Connected components
Cycle detection
Topological sorting
Backtracking
Finding paths
Exploring all possible routes
Recursive graph structures
A useful mental clue is:
"Go as deep as possible, then come back."
Think:
DFS
↓
Depth
↓
Stack / RecursionWhen Should You Use BFS?
BFS is particularly useful when the problem involves:
Shortest path in an unweighted graph
Minimum number of steps
Level-by-level exploration
Finding nodes at a certain distance
Multi-source spreading problems
A useful mental clue is:
"Explore everything nearby before going farther."
Think:
BFS
↓
Breadth
↓
QueueThe Main Idea
Graph traversal means systematically visiting graph vertices.
The two fundamental techniques are:
DFS
Depth-First Search
BFS
Breadth-First SearchDFS
Go deep
↓
Backtrack
↓
ContinueUsually implemented using:
Recursion / StackBFS
Visit current level
↓
Visit next level
↓
Continue outwardImplemented using:
QueueThe most important complexity to remember is:
Using Adjacency List:
DFS → O(V + E)
BFS → O(V + E)And because graphs can contain cycles, always remember the importance of:
visited[]DFS explores deeply through the graph using recursion or a stack, while BFS explores outward level by level using a queue.
Once you understand these two traversals, you're ready for many of the most important graph problems, including connected components, cycle detection, shortest paths, bipartite graphs, topological sorting, and graph-based backtracking.