Graph

Graph Fundamentals

Vertices connected by edges โ€” the most general data structure for modeling relationships. Graphs power social networks, GPS navigation, dependency resolution, and every "shortest path" or "connected components" interview problem.

Foundations ยท Array ยท Linked List ยท Stack ยท Queue ยท HashMap ยท Binary Tree ยท BST ยท Heap ยท Trie ยท Graph ยท Sorting ยท Binary Search ยท Two Pointers ยท Sliding Window ยท Dynamic Prog.
01
Section One ยท Foundation

What is a Graph?

A B C D E 5 vertices, 6 edges โ€” undirected graph

A graph is a collection of vertices (nodes) connected by edges โ€” unlike trees, there's no root, no parent-child hierarchy, and edges can form cycles. A graph can be directed (edges have a direction, like Twitter follows) or undirected (edges are bidirectional, like Facebook friendships). It can be weighted (each edge carries a cost, like road distances) or unweighted. The two dominant storage formats are an adjacency list (each vertex stores a list of neighbors โ€” space-efficient for sparse graphs) and an adjacency matrix (a 2D boolean/weight array โ€” fast edge lookup but O(Vยฒ) space). Graphs are everywhere: social networks, web page links, dependency trees in build systems, flight routes, and the Internet itself. In Java, there's no built-in Graph class โ€” you build one from Map<Integer, List<Integer>> or a 2D array.

Analogy: Think of a city's metro system. Each station is a vertex, each rail connection between two stations is an edge, and the travel time between stations is the edge weight. Some lines are one-way (directed), most are bidirectional (undirected). Finding the fastest route from station A to station B is the shortest-path problem โ€” exactly what BFS and Dijkstra solve. If you need to check whether every station is reachable from any other station, that's the connected-components problem. The metro map IS a graph.
02
Section Two ยท Mental Model

How It Thinks

No root or hierarchy โ€” any vertex can connect to any other vertex
โ–ถ
Cycles are possible โ€” every traversal must track visited nodes or it loops forever
Adjacency list stores only existing edges (Map<V, List<V>>)
โ–ถ
O(V + E) space โ€” efficient for sparse graphs where E << Vยฒ, which covers most real-world cases
Adjacency matrix stores all possible edges in a Vร—V grid
โ–ถ
O(1) edge lookup but O(Vยฒ) space โ€” only practical when the graph is dense or V is small
BFS explores neighbors level by level using a queue
โ–ถ
Finds shortest path in unweighted graphs โ€” the first time BFS reaches a node, that's the minimum-edge path
DFS explores as deep as possible before backtracking using a stack (or recursion)
โ–ถ
Detects cycles, finds connected components, and enables topological sort โ€” the backbone of dependency resolution
Directed edges impose order (Aโ†’B does not imply Bโ†’A)
โ–ถ
Enables DAGs (directed acyclic graphs) โ€” the foundation for topological sort, build systems, and course scheduling
03
Section Three ยท Operations

Core Operations

Add Vertex & Edge
Insert a new vertex into the graph and connect it to existing vertices by adding edges to the adjacency list.
Pseudocode
function addVertex(graph, v): if v not in graph: graph[v] = [] // empty neighbor list // O(1) function addEdge(graph, u, v): graph[u].add(v) // u โ†’ v graph[v].add(u) // v โ†’ u (omit for directed) // O(1)
Add Edge โ€” connecting vertex D to vertex A in an undirected graph
BEFORE A B C D A:[B] B:[A,C] C:[B,D] D:[C] AFTER โ€” addEdge(D, A) A B C D A:[B,D] B:[A,C] C:[B,D] D:[C,A]
Undirected = two directed entries:
  • For undirected graphs, every addEdge(u, v) adds v to u's list AND u to v's list.
  • Forgetting one direction is the #1 graph construction bug โ€” BFS/DFS will miss paths because the edge only works one way.
BFS (Breadth-First Search)
Explore all neighbors at the current depth before moving deeper โ€” guarantees shortest path (minimum edges) in unweighted graphs.
Pseudocode
function bfs(graph, start): visited = {start} queue = [start] while queue is not empty: node = queue.dequeue() process(node) for each neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.enqueue(neighbor) // O(V + E)
BFS โ€” level-by-level exploration from vertex A
Level 0 Level 1 Level 2 A start B D C E Queue: [A] Queue: [B, D] Queue: [D, C, E] Queue: [C, E] Queue: [E] Queue: [] Visit order: A โ†’ B โ†’ D โ†’ C โ†’ E shortest distances from A: B=1, D=1, C=2, E=2 BFS visits all level-1 nodes before any level-2 node โ€” guarantees shortest path
Mark visited BEFORE enqueueing:
  • Add a node to visited when you enqueue it, not when you dequeue it.
  • If you wait until dequeue, multiple neighbors can enqueue the same node simultaneously, causing duplicate processing and incorrect shortest-path distances.
DFS (Depth-First Search)
Explore as far as possible along each branch before backtracking โ€” the foundation for cycle detection, topological sort, and connected components.
Pseudocode
function dfs(graph, node, visited): visited.add(node) process(node) for each neighbor in graph[node]: if neighbor not in visited: dfs(graph, neighbor, visited) // O(V + E)
DFS โ€” depth-first exploration from vertex A
โ‘  โ‘ก โ‘ข backtrack to B โ‘ฃ visit E โ‘ค backtrack, visit D A B C E D DFS order: A โ†’ B โ†’ C โ†’ E โ†’ D goes deep first: Aโ†’Bโ†’C backtracks to B, visits E backtracks to A, visits D DFS does NOT find shortest path it finds A path, not THE shortest path use BFS for shortest path in unweighted graphs DFS uses O(V) stack space โ€” recursive calls match depth of exploration
Iterative DFS with explicit stack:
  • For deep graphs, recursion can cause StackOverflowError.
  • Replace the call stack with an explicit Deque<Integer> โ€” push neighbors, pop next node.
  • The iterative version handles graphs with thousands of nodes that recursive DFS cannot.
Common Mistake: Forgetting the visited set in graphs with cycles. In a tree, DFS/BFS naturally terminates because there are no cycles. In a graph, revisiting a node means infinite recursion (DFS) or infinite loop (BFS). Always initialize a visited set before traversal โ€” this is the single most common graph bug.
04
Section Four ยท Implementation

Build It Once

Build this from scratch once โ€” it makes the mechanics concrete. In any real project or interview, reach for the built-in (Section 05).

Java โ€” Graph core mechanics (adjacency list)
class Graph { private Map<Integer, List<Integer>> adj = new HashMap<>(); // ADD EDGE โ€” O(1) void addEdge(int u, int v) { adj.computeIfAbsent(u, k -> new ArrayList<>()).add(v); adj.computeIfAbsent(v, k -> new ArrayList<>()).add(u); } // BFS โ€” O(V + E) List<Integer> bfs(int start) { List<Integer> order = new ArrayList<>(); Set<Integer> visited = new HashSet<>(); Queue<Integer> queue = new LinkedList<>(); visited.add(start); queue.add(start); while (!queue.isEmpty()) { int node = queue.poll(); order.add(node); for (int neighbor : adj.getOrDefault(node, List.of())) { if (visited.add(neighbor)) // add returns false if already present queue.add(neighbor); } } return order; } // DFS โ€” O(V + E) void dfs(int node, Set<Integer> visited, List<Integer> order) { visited.add(node); order.add(node); for (int neighbor : adj.getOrDefault(node, List.of())) { if (!visited.contains(neighbor)) dfs(neighbor, visited, order); } } }
05
Section Five ยท Java Built-in

Use It In Java

IN JAVA โ€” No built-in Graph; build from Map + List
Use Map<Integer, List<Integer>> (adjacency list) or int[][] (adjacency matrix)
Import import java.util.*;
Pattern Complexity Note
map.computeIfAbsent(u, k -> new ArrayList<>()).add(v) O(1) Add edge โ€” creates list if vertex is new
map.getOrDefault(u, List.of()) O(1) Get neighbors โ€” avoids null check
new LinkedList<>() + poll() O(1) Queue for BFS โ€” use ArrayDeque for better perf
new HashSet<>() + add() O(1) Visited set โ€” add() returns false if duplicate
boolean[][] matrix O(1) lookup Adjacency matrix โ€” when V โ‰ค ~1000
int[][] edges O(E) Edge list input โ€” convert to adj list first
int[] parent (Union-Find) O(ฮฑ(n)) Connected components without BFS/DFS
โš  Gotcha: LeetCode graph problems often give edges as int[][] edges (edge list), not an adjacency list. Always convert to Map<Integer, List<Integer>> first โ€” iterating an edge list per query is O(E) per call, while an adjacency list gives O(degree) per call. The conversion loop is 3 lines; skipping it makes BFS/DFS O(VยทE) instead of O(V+E).
Choose Use Map<Integer, List<Integer>> for sparse graphs (most interview problems). Use boolean[][] adjacency matrix only when V is small (< 1000) and you need O(1) edge existence checks โ€” like Floyd-Warshall or dense graph problems.
06
Section Six ยท Decision Guide

Pick The Right Structure

Decision Flowchart
Shortest path? weighted edges? Yes Weighted? Yes Dijkstra No BFS No Ordering / dependencies? Connectivity? cycles? components? DFS / Union-Find Yes Topological Sort No DFS / BFS BFS = shortest path (unweighted) ยท DFS = connectivity, cycles ยท Dijkstra = shortest path (weighted)
Comparison Table
Representation Edge Lookup Add Edge Space Best For
Adjacency List โ† this O(degree) O(1) O(V + E) Sparse graphs, most interview problems
Adjacency Matrix O(1) O(1) O(Vยฒ) Dense graphs, Floyd-Warshall
Edge List O(E) O(1) O(E) Kruskal's MST, input format only
Union-Find O(ฮฑ(n)) O(ฮฑ(n)) O(V) Dynamic connectivity, MST
07
Section Seven ยท Practice

Problems To Solve

Graph interview problems cluster around three patterns: BFS for shortest-path and level-order problems, DFS for connectivity and cycle detection, and topological sort for dependency ordering. Most medium-difficulty graph problems reduce to "build an adjacency list, then BFS or DFS" โ€” the hard part is recognizing that the problem is a graph problem and correctly modeling the vertices and edges.

Difficulty Pattern Problem Key Insight
Medium bfs Number of Islands โ€” LC 200 Treat each '1' cell as a vertex with edges to adjacent '1' cells. BFS/DFS from each unvisited '1' marks an entire island โ€” count how many times you start a new traversal.
Medium bfs Rotting Oranges โ€” LC 994 Multi-source BFS: enqueue all rotten oranges at once (level 0), then BFS spreads rot level by level. The answer is the number of BFS levels โ€” each level is one minute.
Medium dfs Clone Graph โ€” LC 133 DFS with a HashMap mapping old node โ†’ new node. When you visit a neighbor, check the map: if it exists, reuse it; if not, create it and recurse. The map prevents infinite loops on cycles.
Medium dfs Course Schedule โ€” LC 207 Model courses as vertices, prerequisites as directed edges. Cycle in a directed graph = impossible to finish all courses. Use DFS with three states (unvisited, in-progress, done) to detect back edges.
Medium bfs 01 Matrix โ€” LC 542 Multi-source BFS from all '0' cells simultaneously. Each level increments distance by 1. Initialize the queue with all zeros, mark all ones as unvisited โ€” BFS fills in the correct distances layer by layer.
Hard bfs Word Ladder โ€” LC 127 Each word is a vertex; edges connect words differing by one character. BFS from beginWord finds shortest transformation sequence. The trick: generate all possible one-character mutations instead of comparing every pair of words.
Interview Pattern:
  • BFS and DFS each solve 40% of graph problems.
  • If the problem asks for shortest path, minimum steps, or nearest distance โ€” use BFS.
  • If it asks for "can you reach", cycle detection, connected components, or topological order โ€” use DFS.
  • The remaining 20% require Dijkstra (weighted shortest path) or Union-Find (dynamic connectivity).
  • Build the adjacency list first; the traversal is boilerplate.

โ†’ See all Graph problems with full solutions