Why is the time complexity of both DFS and BFS O( V + E )
Categories:
Understanding O(V + E) Time Complexity in DFS and BFS

Explore why Depth-First Search (DFS) and Breadth-First Search (BFS) algorithms exhibit O(V + E) time complexity, a fundamental concept in graph theory.
Depth-First Search (DFS) and Breadth-First Search (BFS) are two of the most fundamental algorithms for traversing or searching graph or tree data structures. Despite their different exploration strategies—DFS goes deep before backtracking, while BFS explores level by level—both algorithms share the same asymptotic time complexity: O(V + E), where 'V' represents the number of vertices (nodes) and 'E' represents the number of edges in the graph. This article delves into the reasons behind this shared complexity, breaking down how each component (V and E) contributes to the overall runtime.
The Role of Vertices (V)
The 'V' component in O(V + E) accounts for the work done at each vertex. Both DFS and BFS must visit every vertex in the graph at least once (assuming the graph is connected from the starting point). When a vertex is visited, the algorithm typically performs a constant amount of work:
- Marking as visited: To prevent infinite loops in graphs with cycles and to ensure each vertex is processed only once, algorithms mark vertices as 'visited'. This is a constant-time operation.
- Adding to a data structure: DFS uses a stack (either explicitly or implicitly via recursion), and BFS uses a queue. Adding a vertex to these structures is typically a constant-time operation.
- Processing data: Any application-specific processing for that vertex (e.g., printing its value, checking a condition) is also considered constant time per vertex for the purpose of complexity analysis.
Since each of the 'V' vertices is visited and processed a constant number of times, the total time spent on vertex-related operations is proportional to V, hence O(V).
flowchart TD A[Start Traversal] B{Is current vertex V visited?} C[Mark V as visited] D[Perform constant work on V] E[Add V to stack/queue (DFS/BFS)] F[Explore neighbors of V] A --> B B -- No --> C C --> D D --> E E --> F B -- Yes --> G[Skip V] F --> H{Are all vertices processed?} H -- No --> A H -- Yes --> I[End Traversal]
Flowchart illustrating the processing of a single vertex in DFS/BFS.
The Role of Edges (E)
The 'E' component accounts for the work done traversing the edges. Both DFS and BFS explore the edges to find unvisited neighbors. The way this exploration happens depends on the graph's representation:
Adjacency List Representation: This is the most common and efficient representation for sparse graphs (graphs with relatively few edges compared to the maximum possible). For each vertex
u
, we iterate through its adjacency list to find its neighbors. Ifu
hasdeg(u)
neighbors, iterating through its list takesO(deg(u))
time. Since each edge(u, v)
appears inu
's adjacency list andv
's adjacency list (for undirected graphs) or justu
's list (for directed graphs), each edge is effectively 'looked at' a constant number of times across all adjacency list traversals. Summingdeg(u)
for allu
gives2E
for undirected graphs andE
for directed graphs. Thus, the total time spent iterating through adjacency lists is proportional to E, hence O(E).Adjacency Matrix Representation: In this representation, checking for an edge between
u
andv
takesO(1)
time. However, to find all neighbors ofu
, we must iterate through allV
possible columns (or rows) inu
's row (or column) in the matrix. This takesO(V)
time for each vertex. If we do this for allV
vertices, the edge-related work becomesO(V*V)
orO(V^2)
, which is less efficient thanO(E)
for sparse graphs. However, for dense graphs (whereE
approachesV^2
),O(V^2)
is equivalent toO(E)
.
Given that adjacency lists are generally preferred for graph traversals due to their efficiency with sparse graphs, the O(E)
term is derived from iterating through these lists.
Combining V and E: Why O(V + E)?
The total time complexity is the sum of the work done on vertices and the work done on edges. Each vertex is visited once, and each edge is examined a constant number of times (twice for undirected graphs in an adjacency list representation, once for directed graphs). Therefore, the total time complexity is O(V + E)
.
It's important to note that V
and E
are not always independent. In a connected graph:
- The minimum number of edges is
V - 1
(for a tree). - The maximum number of edges is
V * (V - 1) / 2
for an undirected simple graph, orV * (V - 1)
for a directed simple graph.
This means that E
can be as small as O(V)
or as large as O(V^2)
. The O(V + E)
notation accurately captures the complexity across this spectrum, as it accounts for both the vertex processing and the edge traversal, regardless of the graph's density.
from collections import deque
def bfs(graph, start_node):
visited = set()
queue = deque([start_node])
visited.add(start_node)
while queue:
vertex = queue.popleft() # O(1) for deque
print(vertex) # Constant work per vertex
for neighbor in graph[vertex]: # Iterating through edges
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor) # O(1) for deque
def dfs_iterative(graph, start_node):
visited = set()
stack = [start_node]
visited.add(start_node)
while stack:
vertex = stack.pop() # O(1) for list.pop()
print(vertex) # Constant work per vertex
for neighbor in graph[vertex]: # Iterating through edges
if neighbor not in visited:
visited.add(neighbor)
stack.append(neighbor) # O(1) for list.append()
# Example Graph (Adjacency List)
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
print("BFS Traversal:")
bfs(graph, 'A')
print("\nDFS Traversal (Iterative):")
dfs_iterative(graph, 'A')
Python implementation of BFS and iterative DFS, highlighting vertex and edge operations.
O(V + E)
complexity holds true for both connected and disconnected graphs. If the graph is disconnected, the algorithm might need to be run from multiple starting points (one for each connected component) to visit all vertices and edges, but the total complexity remains O(V + E)
.