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.Startfrom source, explore all neighbors at distance 1, then distance 2, etc.Guarantees shortest path in unweighted graphs.
Example
// JavaScriptfunctionbfs(graph, start){const visited =newSet([start]);const queue =[start];const order =[];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);}}}return order;}
# 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 not in visited:
visited.add(neighbor)
queue.append(neighbor)return order
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.Goas deep as possible before backtracking.Usefulfor: connectivity, cycle detection, topological sort.
Example
// JavaScript - Iterativefunctiondfs(graph, start){const visited =newSet();const stack =[start];const order =[];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);}}return order;}
# Python-Recursive
def dfs(graph, start, visited=None):if visited isNone:
visited =set()
visited.add(start)
order =[start]for neighbor in graph.get(start,[]):if neighbor not in visited:
order.extend(dfs(graph, neighbor, visited))return order
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:Track3 states - unvisited,in current path, completed.
Example
// JavaScript - Directed graph cycle detectionfunctionhasCycleDirected(graph, numNodes){constWHITE=0,GRAY=1,BLACK=2;const color =newArray(numNodes).fill(WHITE);functiondfs(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 graph
def has_cycle_directed(graph, num_nodes):WHITE,GRAY,BLACK=0,1,2
color =[WHITE]* num_nodes
def dfs(node):
color[node]=GRAYfor neighbor in graph.get(node,[]):if color[neighbor]==GRAY:returnTrueif color[neighbor]==WHITE and dfs(neighbor):returnTrue
color[node]=BLACKreturnFalsereturnany(color[i]==WHITE and dfs(i)for i inrange(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.
RunBFS or DFSfrom each unvisited node.Each run discovers one connected component.Count the number of runs = number of components.
Example
// JavaScriptfunctioncountComponents(n, edges){const graph =newMap();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 =newSet();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;}
# Python
def 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 inrange(n):if i not in visited:
components +=1
queue =deque([i])
visited.add(i)while queue:
node = queue.popleft()for nb in graph[node]:if nb not in 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.
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 approachfunctiondijkstra(graph, start, n){const dist =newArray(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 DFSfrom each unvisited '1'.Count how many searches you start.
Example
// JavaScriptfunctionnumIslands(grid){if(!grid.length)return0;const rows = grid.length, cols = grid[0].length;let count =0;functiondfs(r, c){if(r <0|| r >= rows || c <0|| c >= cols || grid[r][c]!=='1')return;
grid[r][c]='0';// mark visiteddfs(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;}
# Python
def num_islands(grid):if not grid:return0
rows, cols =len(grid),len(grid[0])
count =0
def dfs(r, c):if r <0 or r >= rows or c <0 or 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 inrange(rows):for c inrange(cols):if grid[r][c]=='1':
count +=1dfs(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