Note Space: O(V + E). Adjacency list is preferred over adjacency matrix for sparse graphs (most interview problems). Always ask: directed or undirected? Weighted or unweighted? Can there be self-loops or parallel edges?
adjacency listgraph representationbuild graphedge list to graph
Graph BFS
syntax
Queue + visited set.
Start from source, explore all neighbors at distance 1, then distance 2, etc.
Guarantees shortest path in unweighted graphs.
example
// JavaScriptfunction bfs(graph, start) {
const visited = new Set([start]);
const queue = [start];
constorder = [];
while (queue.length > 0) {
const node = queue.shift();
order.push(node);
for (const neighbor of (graph.get(node) || [])) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
returnorder;
}
# Pythonfrom collections import deque
def bfs(graph, start):
visited = {start}
queue = deque([start])
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph.get(node, []):
if neighbor notin visited:
visited.add(neighbor)
queue.append(neighbor)
returnorder
output
bfs(graph, 0) → [0, 1, 2, 3]
Note Time O(V + E), Space O(V). Mark visited WHEN ENQUEUING, not when dequeuing — this prevents duplicate queue entries. For shortest path distance, track a distance array. BFS on a grid: treat each cell as a node with 4 directional neighbors.
graph BFSbreadth first search graphshortest path unweightedBFS traversal
Graph DFS
syntax
Stack (or recursion) + visited set.
Go as deep as possible before backtracking.
Useful for: connectivity, cycle detection, topological sort.
example
// JavaScript — Iterativefunction dfs(graph, start) {
const visited = new Set();
const stack = [start];
constorder = [];
while (stack.length > 0) {
const node = stack.pop();
if (visited.has(node)) continue;
visited.add(node);
order.push(node);
for (const neighbor of (graph.get(node) || [])) {
if (!visited.has(neighbor)) stack.push(neighbor);
}
}
returnorder;
}
# Python — Recursivedef dfs(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
order = [start]
for neighbor in graph.get(start, []):
if neighbor notin visited:
order.extend(dfs(graph, neighbor, visited))
returnorder
output
dfs(graph, 0) → [0, 2, 3, 1] (one possible order)
Note Time O(V + E), Space O(V). Recursive DFS risks stack overflow on very deep graphs — iterative is safer for large inputs. DFS visit order depends on neighbor iteration order. For interview purposes, both iterative and recursive should be in your toolkit.
graph DFSdepth first search graphDFS traversalrecursive DFS
Detect Cycle in Graph
syntax
Undirected: DFS — if we visit a node already visited (and it's not the parent), cycle exists.
Directed: Track 3 states — unvisited, in current path, completed.
example
// JavaScript — Directed graph cycle detectionfunction hasCycleDirected(graph, numNodes) {
const WHITE = 0, GRAY = 1, BLACK = 2;
const color = new Array(numNodes).fill(WHITE);
function dfs(node) {
color[node] = GRAY; // in current pathfor (const neighbor of (graph.get(node) || [])) {
if (color[neighbor] === GRAY) returntrue; // back edge = cycleif (color[neighbor] === WHITE && dfs(neighbor)) returntrue;
}
color[node] = BLACK; // completedreturnfalse;
}
for (let i = 0; i < numNodes; i++) {
if (color[i] === WHITE && dfs(i)) returntrue;
}
returnfalse;
}
# Python — Directed graphdef has_cycle_directed(graph, num_nodes):
WHITE, GRAY, BLACK = 0, 1, 2
color = [WHITE] * num_nodes
def dfs(node):
color[node] = GRAY
for neighbor in graph.get(node, []):
if color[neighbor] == GRAY:
returnTrueif color[neighbor] == WHITE and dfs(neighbor):
returnTrue
color[node] = BLACK
returnFalsereturn any(color[i] == WHITE and dfs(i) for i in range(num_nodes))
output
Cycle: 0→1→2→0 → True
No cycle: 0→1→2 → False
Note Time O(V + E), Space O(V). The three-color technique (white/gray/black) is standard for directed graphs. Gray = currently on the recursion stack. A back edge to a gray node proves a cycle. For undirected graphs, simply track parent to avoid false positives.
Note Time O(V + E), Space O(V). If the result has fewer nodes than the graph, a cycle exists (useful for course schedule problems). Kahn's is easier to implement and also detects cycles. Multiple valid orderings may exist.
Run BFS or DFS from each unvisited node.
Each run discovers one connected component.
Count the numberof runs = numberof components.
example
// JavaScriptfunction countComponents(n, edges) {
const graph = new Map();
for (let i = 0; i < n; i++) graph.set(i, []);
for (const [u, v] of edges) {
graph.get(u).push(v);
graph.get(v).push(u);
}
const visited = new Set();
let components = 0;
for (let i = 0; i < n; i++) {
if (!visited.has(i)) {
components++;
const queue = [i];
visited.add(i);
while (queue.length) {
const node = queue.shift();
for (const nb of graph.get(node)) {
if (!visited.has(nb)) {
visited.add(nb);
queue.push(nb);
}
}
}
}
}
return components;
}
# Pythondef count_components(n, edges):
from collections import defaultdict, deque
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = set()
components = 0for i in range(n):
if i notin visited:
components += 1
queue = deque([i])
visited.add(i)
while queue:
node = queue.popleft()
for nb in graph[node]:
if nb notin visited:
visited.add(nb)
queue.append(nb)
return components
output
n=5, edges=[[0,1],[1,2],[3,4]] → 2 components ({0,1,2} and {3,4})
Note Time O(V + E), Space O(V). Alternative: Union-Find achieves the same result and is better for dynamic connectivity. This pattern is the basis for 'number of islands' and 'number of provinces' problems.
connected componentsnumber of componentsgraph connectivityunion find
Shortest Path — Dijkstra's Concept
syntax
For weighted graphs with non-negative edges.
Use a min-heap (priority queue).
Greedily expand the nearest unvisited node.
example
// JavaScript — using a simple priority queue approachfunction dijkstra(graph, start, n) {
const dist = new Array(n).fill(Infinity);
dist[start] = 0;
// Min-heap: [distance, node]const pq = [[0, start]];
while (pq.length > 0) {
pq.sort((a, b) => a[0] - b[0]); // simplified; use real heapconst [d, u] = pq.shift();
if (d > dist[u]) continue;
for (const [v, w] of (graph.get(u) || [])) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push([dist[v], v]);
}
}
}
return dist;
}
# Pythonimport heapq
def dijkstra(graph, start, n):
dist = [float('inf')] * n
dist[start] = 0
pq = [(0, start)] # (distance, node)while pq:
d, u = heapq.heappop(pq)
if d > dist[u]:
continuefor v, w in graph.get(u, []):
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(pq, (dist[v], v))
return dist
output
Graph: 0→1(4), 0→2(1), 2→1(2)
dijkstra(graph, 0) → [0, 3, 1] (shortest to node 1 is via node 2)
Note Time O((V + E) log V) with binary heap. Does NOT work with negative edge weights (use Bellman-Ford for that). The 'if d > dist[u]: continue' line is the lazy deletion optimization — critical for performance. For unweighted graphs, BFS is simpler and sufficient.
Treat grid as a graph. Each '1' cell is a node, connected to 4-directional '1' neighbors.
BFS or DFS from each unvisited '1'. Count how many searches you start.
example
// JavaScriptfunction numIslands(grid) {
if (!grid.length) return0;
const rows = grid.length, cols = grid[0].length;
let count = 0;
function dfs(r, c) {
if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] !== '1') return;
grid[r][c] = '0'; // mark visited
dfs(r + 1, c); dfs(r - 1, c);
dfs(r, c + 1); dfs(r, c - 1);
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (grid[r][c] === '1') {
count++;
dfs(r, c);
}
}
}
return count;
}
# Pythondef num_islands(grid):
ifnot grid:
return0
rows, cols = len(grid), len(grid[0])
count = 0def dfs(r, c):
if r < 0or r >= rows or c < 0or c >= cols or grid[r][c] != '1':
return
grid[r][c] = '0'
dfs(r + 1, c); dfs(r - 1, c)
dfs(r, c + 1); dfs(r, c - 1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
count += 1
dfs(r, c)
return count
output
Grid:
1 1 0 0
1 0 0 1
0 0 1 1
→ 3 islands
Note Time O(rows * cols), Space O(rows * cols) worst case for recursion stack. Modifying the input grid to mark visited saves space but is destructive — ask the interviewer if that is acceptable. BFS alternative avoids deep recursion. Variants: max area of island, surrounded regions.
number of islandsgrid DFSgrid BFSisland countingflood fill