Algorithms March 25, 2026 14 min read

Graph Algorithms in Interviews: From Theory to Implementation

David Park

David Park

Product Manager

Graph algorithms are among the most feared - and most frequently tested - topics in technical interviews. From social network connections to GPS navigation, graphs underpin some of the most important systems in modern computing. Yet many candidates freeze when confronted with a graph problem, often because they never bridged the gap between textbook theory and practical implementation. This comprehensive guide will take you from foundational concepts to interview-ready code, covering BFS, DFS, shortest-path algorithms, topological sorting, and Union-Find - everything you need to tackle graph questions with confidence.

Abstract network of interconnected nodes representing graph data structures

Why Graphs Matter in Technical Interviews

Graphs aren't just an academic curiosity - they model relationships in nearly every domain. Social networks are graphs. File systems are trees (a special graph). The internet itself is a massive directed graph. Interviewers love graph problems because they test a candidate's ability to think abstractly, choose the right traversal strategy, and handle edge cases like cycles, disconnected components, and weighted edges.

According to data from major interview platforms, graph-related questions appear in roughly25-30% of all coding interviews at FAANG companies. Yet candidates report feeling least prepared for these problems compared to arrays, strings, or even dynamic programming.

๐Ÿ“Š Graph Questions by Company (2024-2025 Data)

Google averages 2-3 graph questions per interview loop. Meta focuses heavily on BFS/DFS in their coding rounds. Amazon frequently tests shortest-path and topological sort in system design contexts. Understanding graphs isn't optional - it's essential.

Before we dive into specific algorithms, let's make sure the fundamentals are solid. A graphG = (V, E) consists of a set of vertices (nodes) and a set ofedges (connections). Graphs can be directed or undirected, weighted or unweighted, cyclic or acyclic. Your choice of representation - adjacency list vs. adjacency matrix - has significant implications for time and space complexity.

RepresentationSpaceEdge LookupIterate NeighborsBest For
Adjacency ListO(V + E)O(degree)O(degree)Sparse graphs, most interview problems
Adjacency MatrixO(Vยฒ)O(1)O(V)Dense graphs, frequent edge queries
Edge ListO(E)O(E)O(E)Kruskal's algorithm, simple storage
"If you understand BFS and DFS deeply - really deeply - you can solve 80% of graph problems in interviews. Everything else is built on those two traversals."
- Gayle Laakmann McDowell, Author of Cracking the Coding Interview

BFS Deep Dive: Level-Order Thinking

Breadth-First Search (BFS) explores a graph layer by layer, visiting all neighbors of the current node before moving to the next level. It uses a queue (FIFO) data structure and is the go-to algorithm for finding the shortest path in unweighted graphs.

Core BFS Template

function bfs(graph, start) {
  const visited = new Set();
  const queue = [start];
  visited.add(start);

  while (queue.length > 0) {
    const node = queue.shift();
    // Process node here

    for (const neighbor of graph[node]) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);
      }
    }
  }
}

The key insight with BFS is that when you first reach a node, you've found the shortest path to it (in terms of number of edges). This property makes BFS ideal for problems like:

  • โ†’Shortest path in unweighted graphs - BFS guarantees the minimum number of edges
  • โ†’Level-order traversal - processing nodes layer by layer (e.g., binary tree levels)
  • โ†’Multi-source BFS - starting from multiple nodes simultaneously (e.g., "rotting oranges")
  • โ†’Bipartite checking - 2-coloring a graph using BFS layers
  • โ†’Word ladder problems - finding shortest transformation sequences

Multi-Source BFS Example

A classic interview problem: given a grid where 1 represents land and 0 represents water, find the distance of each land cell to the nearest water cell. The trick is to start BFS from all water cells simultaneously:

function maxDistance(grid) {
  const n = grid.length;
  const queue = [];
  
  // Add all water cells as starting points
  for (let i = 0; i < n; i++) {
    for (let j = 0; j < n; j++) {
      if (grid[i][j] === 0) {
        queue.push([i, j, 0]);
      }
    }
  }
  
  const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
  let maxDist = -1;
  
  while (queue.length > 0) {
    const [r, c, dist] = queue.shift();
    for (const [dr, dc] of dirs) {
      const nr = r + dr, nc = c + dc;
      if (nr >= 0 && nr < n && nc >= 0 && nc < n 
          && grid[nr][nc] === 1) {
        grid[nr][nc] = 0; // mark visited
        maxDist = Math.max(maxDist, dist + 1);
        queue.push([nr, nc, dist + 1]);
      }
    }
  }
  return maxDist;
}
Developer working through algorithm problems on a whiteboard

DFS Patterns: The Recursive Powerhouse

Depth-First Search (DFS) explores as deep as possible along each branch before backtracking. It uses a stack (either explicitly or via recursion) and is incredibly versatile. While BFS is about breadth, DFS is about depth - and it excels at problems involving paths, cycles, connected components, and backtracking.

Recursive vs. Iterative DFS

// Recursive DFS
function dfsRecursive(graph, node, visited) {
  visited.add(node);
  // Process node here
  
  for (const neighbor of graph[node]) {
    if (!visited.has(neighbor)) {
      dfsRecursive(graph, neighbor, visited);
    }
  }
}

// Iterative DFS
function dfsIterative(graph, start) {
  const visited = new Set();
  const stack = [start];
  
  while (stack.length > 0) {
    const node = stack.pop();
    if (visited.has(node)) continue;
    visited.add(node);
    // Process node here
    
    for (const neighbor of graph[node]) {
      if (!visited.has(neighbor)) {
        stack.push(neighbor);
      }
    }
  }
}

โšก When to Use Iterative DFS

Use iterative DFS when the graph could be very deep (risk of stack overflow with recursion), when you need explicit control over the traversal order, or when the problem constraints indicate up to 10โต or more nodes. Python's default recursion limit is 1000, and even JavaScript can hit limits around 10,000-15,000 frames.

DFS Pattern: Cycle Detection in Directed Graphs

Detecting cycles is a classic DFS application. In a directed graph, you need three states: unvisited, in the current path (gray), and fully processed (black). A back edge to a gray node indicates a cycle:

function hasCycle(graph, n) {
  const WHITE = 0, GRAY = 1, BLACK = 2;
  const color = new Array(n).fill(WHITE);

  function dfs(node) {
    color[node] = GRAY;
    for (const neighbor of graph[node]) {
      if (color[neighbor] === GRAY) return true;  // cycle!
      if (color[neighbor] === WHITE && dfs(neighbor)) {
        return true;
      }
    }
    color[node] = BLACK;
    return false;
  }

  for (let i = 0; i < n; i++) {
    if (color[i] === WHITE && dfs(i)) return true;
  }
  return false;
}

DFS Pattern: Connected Components

Counting connected components is another bread-and-butter DFS application. Simply iterate over all nodes, and each time you find an unvisited node, run DFS from it and increment your component count:

  • โ†’Number of Islands - treat the grid as a graph, DFS from each unvisited land cell
  • โ†’Friend Circles - each DFS from an unvisited person discovers one friend group
  • โ†’Accounts Merge - find connected components among overlapping email sets
  • โ†’Graph Valid Tree - a tree is a connected acyclic graph with exactly V-1 edges

Shortest Path Algorithms

Once edges have weights, BFS alone won't cut it. You need algorithms specifically designed for weighted graphs. The two most important ones for interviews are Dijkstra's algorithm (non-negative weights) and Bellman-Ford (handles negative weights).

Complex network visualization showing weighted connections between nodes

Dijkstra's Algorithm

Dijkstra's uses a priority queue (min-heap) to always process the closest unvisited node. It's greedy and requires all edge weights to be non-negative.

function dijkstra(graph, start, n) {
  const dist = new Array(n).fill(Infinity);
  dist[start] = 0;
  // MinHeap: [distance, node]
  const pq = new MinPriorityQueue();
  pq.enqueue([0, start]);

  while (!pq.isEmpty()) {
    const [d, u] = pq.dequeue();
    if (d > dist[u]) continue; // stale entry

    for (const [v, weight] of graph[u]) {
      const newDist = dist[u] + weight;
      if (newDist < dist[v]) {
        dist[v] = newDist;
        pq.enqueue([newDist, v]);
      }
    }
  }
  return dist;
}
AlgorithmTime ComplexityNegative Weights?Negative Cycles?Use Case
BFSO(V + E)N/A (unweighted)N/AUnweighted shortest path
DijkstraO((V+E) log V)โŒ NoโŒ NoNon-negative weighted graphs
Bellman-FordO(V ร— E)โœ… YesDetects themNegative weights, cycle detection
Floyd-WarshallO(Vยณ)โœ… YesDetects themAll-pairs shortest path
"In my 15 years of interviewing at Google, I've never seen a candidate fail because they didn't know Floyd-Warshall. But I've seen hundreds fail because they couldn't implement BFS or Dijkstra correctly under pressure."
- Alex Xu, Author of System Design Interview

Topological Sort: Ordering Dependencies

Topological sorting is a linear ordering of vertices in a Directed Acyclic Graph (DAG) such that for every directed edge u โ†’ v, vertex u comes before v. It's essential for dependency resolution - think build systems, course prerequisites, or task scheduling.

Kahn's Algorithm (BFS-based)

Kahn's algorithm uses in-degree counting and a queue. It's intuitive and also detects cycles (if you can't process all nodes, there's a cycle):

function topologicalSort(graph, n) {
  const inDegree = new Array(n).fill(0);
  
  // Calculate in-degrees
  for (let u = 0; u < n; u++) {
    for (const v of graph[u]) {
      inDegree[v]++;
    }
  }
  
  // Start with all zero in-degree nodes
  const queue = [];
  for (let i = 0; i < n; i++) {
    if (inDegree[i] === 0) queue.push(i);
  }
  
  const result = [];
  while (queue.length > 0) {
    const node = queue.shift();
    result.push(node);
    
    for (const neighbor of graph[node]) {
      inDegree[neighbor]--;
      if (inDegree[neighbor] === 0) {
        queue.push(neighbor);
      }
    }
  }
  
  // If result doesn't contain all nodes, there's a cycle
  return result.length === n ? result : [];
}

๐ŸŽฏ Classic Topological Sort Interview Problems

  • โ€ข Course Schedule I & II - Can you finish all courses? In what order?
  • โ€ข Alien Dictionary - Derive letter ordering from sorted alien words
  • โ€ข Build Order - Determine project compilation order given dependencies
  • โ€ข Parallel Courses - Minimum semesters to complete all courses
  • โ€ข Sequence Reconstruction - Verify if a sequence is uniquely reconstructible
Server infrastructure and data flow representing algorithm processing

Union-Find (Disjoint Set Union)

Union-Find is a data structure that tracks elements partitioned into disjoint sets. It supports two primary operations: Find (which set does an element belong to?) and Union (merge two sets). With path compression and union by rank, both operations run in near-constant time - O(ฮฑ(n)), where ฮฑ is the inverse Ackermann function.

Optimized Union-Find Implementation

class UnionFind {
  constructor(n) {
    this.parent = Array.from({ length: n }, (_, i) => i);
    this.rank = new Array(n).fill(0);
    this.components = n;
  }

  find(x) {
    if (this.parent[x] !== x) {
      this.parent[x] = this.find(this.parent[x]); // path compression
    }
    return this.parent[x];
  }

  union(x, y) {
    const px = this.find(x), py = this.find(y);
    if (px === py) return false; // already connected
    
    // union by rank
    if (this.rank[px] < this.rank[py]) {
      this.parent[px] = py;
    } else if (this.rank[px] > this.rank[py]) {
      this.parent[py] = px;
    } else {
      this.parent[py] = px;
      this.rank[px]++;
    }
    this.components--;
    return true;
  }

  connected(x, y) {
    return this.find(x) === this.find(y);
  }
}

Union-Find shines in problems where you need to dynamically track connectivity. It's often the cleanest solution for problems that can also be solved with DFS/BFS but where you're processing edges incrementally:

  • โ†’Number of Connected Components - process edges and count remaining components
  • โ†’Redundant Connection - find the edge that creates a cycle (the union that fails)
  • โ†’Accounts Merge - group accounts sharing common emails
  • โ†’Kruskal's MST - sort edges by weight, union if not connected
  • โ†’Earliest Moment When Everyone Becomes Friends - process timestamps in order

๐Ÿ’ก BFS/DFS vs. Union-Find: When to Choose What

Use BFS/DFS when you need to traverse paths, compute distances, or the graph is given as an adjacency list. Use Union-Find when you're processing edges one at a time, need to check/merge components dynamically, or when the problem asks about connectivity after adding edges. Union-Find is also preferred when you need to track the number of components efficiently.

Practice Problems: Your Study Roadmap

Theory without practice is useless. Here's a curated list of problems organized by difficulty and technique. Start with the Easy/Medium problems to build pattern recognition, then graduate to the Hard ones.

ProblemDifficultyTechniqueKey Insight
Number of Islands๐ŸŸข MediumDFS/BFSGrid as graph, sink visited cells
Clone Graph๐ŸŸข MediumBFS/DFS + HashMapMap old nodes to cloned nodes
Course Schedule II๐ŸŸก MediumTopological SortKahn's algorithm with in-degree
Network Delay Time๐ŸŸก MediumDijkstraFind max of all shortest paths
Redundant Connection๐ŸŸก MediumUnion-FindFirst edge where union fails
Word Ladder๐Ÿ”ด HardBFSEach word is a node, BFS for shortest
Alien Dictionary๐Ÿ”ด HardTopological SortBuild graph from adjacent word pairs
Cheapest Flights Within K Stops๐Ÿ”ด HardModified Dijkstra / BFSTrack stops as extra state

Study Strategy

  • โ†’Week 1: Master BFS and DFS templates - solve 5 problems each
  • โ†’Week 2: Tackle topological sort and cycle detection - 4 problems
  • โ†’Week 3: Implement Dijkstra and Union-Find from scratch - 4 problems each
  • โ†’Week 4: Mixed practice - time yourself, simulate interview conditions

The most important thing is to recognize which pattern a problem belongs to. Before writing any code, ask yourself: Is this a shortest-path problem? Does it involve connectivity? Are there dependencies that need ordering? The answer will point you to the right algorithm every time.

Matrix-style code flowing across a screen representing algorithm execution

๐Ÿง  Quick Reference: Which Algorithm to Use

  • โ€ข "Shortest path" + unweighted โ†’ BFS
  • โ€ข "Shortest path" + weighted (non-negative) โ†’ Dijkstra
  • โ€ข "Shortest path" + negative weights โ†’ Bellman-Ford
  • โ€ข "Find all paths" or "is there a path" โ†’ DFS
  • โ€ข "Ordering" or "prerequisites" โ†’ Topological Sort
  • โ€ข "Connected components" or "are X and Y connected" โ†’ Union-Find or DFS
  • โ€ข "Cycle detection" โ†’ DFS (directed) or Union-Find (undirected)
  • โ€ข "Minimum spanning tree" โ†’ Kruskal's (Union-Find) or Prim's

Master Graph Algorithms with AI-Powered Practice

Stop memorizing - start understanding. Devana's AI interview coach walks you through graph problems step by step, identifies your weak patterns, and generates personalized practice sets. From BFS to Dijkstra, get interview-ready in weeks, not months.

Start Practicing Graph Problems โ†’

Free tier available ยท No credit card required ยท AI-powered feedback on every solution