A graph is just a collection of nodes (also called vertices) connected by edges. That’s it. Think of a social network: people are nodes, friendships are edges.
Graphs are everywhere in interviews. Shortest path, connected components, dependency resolution — all graph problems. Before we can solve any of them, we need to know how to represent a graph in code.
Types of Graphs
Representation 1: Adjacency List (Most Common)
This is what we’ll use 90% of the time in interviews. For each node, we store a list of its neighbors.
In simple language, it’s like a phone book. Each person’s entry lists who they’re connected to.
// Build adjacency list from edge list (undirected)
function buildGraph(edges, n) {
const graph = Array.from({ length: n }, () => []);
for (const [u, v] of edges) {
graph[u].push(v); // u connects to v
graph[v].push(u); // v connects to u (undirected)
}
return graph;
}
// edges = [[0,1], [0,2], [1,3]]
// graph = [[1,2], [0,3], [0], [1]]
# Build adjacency list from edge list (undirected)
from collections import defaultdict
def build_graph(edges, n):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v) # u connects to v
graph[v].append(u) # v connects to u (undirected)
return graph
# edges = [[0,1], [0,2], [1,3]]
# graph = {0: [1,2], 1: [0,3], 2: [0], 3: [1]}
// Build adjacency list from edge list (undirected)
List<List<Integer>> buildGraph(int[][] edges, int n) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
for (int[] e : edges) {
graph.get(e[0]).add(e[1]); // u connects to v
graph.get(e[1]).add(e[0]); // v connects to u
}
return graph;
}
For a directed graph, we just skip the second line — only add u → v, not v → u.
For a weighted graph, we store [neighbor, weight] pairs instead of just neighbors:
// Weighted adjacency list
function buildWeightedGraph(edges) {
const graph = new Map();
for (const [u, v, weight] of edges) {
if (!graph.has(u)) graph.set(u, []);
if (!graph.has(v)) graph.set(v, []);
graph.get(u).push([v, weight]);
graph.get(v).push([u, weight]); // skip for directed
}
return graph;
}
# Weighted adjacency list
def build_weighted_graph(edges):
graph = defaultdict(list)
for u, v, weight in edges:
graph[u].append((v, weight))
graph[v].append((u, weight)) # skip for directed
return graph
// Weighted adjacency list — store int[] {neighbor, weight}
Map<Integer, List<int[]>> buildWeightedGraph(int[][] edges) {
Map<Integer, List<int[]>> graph = new HashMap<>();
for (int[] e : edges) {
graph.computeIfAbsent(e[0], k -> new ArrayList<>()).add(new int[]{e[1], e[2]});
graph.computeIfAbsent(e[1], k -> new ArrayList<>()).add(new int[]{e[0], e[2]});
}
return graph;
}
Representation 2: Adjacency Matrix
A 2D array where matrix[i][j] = 1 means there’s an edge from node i to node j. For weighted graphs, store the weight instead of 1.
// Build adjacency matrix (undirected)
function buildMatrix(edges, n) {
const matrix = Array.from({ length: n }, () => Array(n).fill(0));
for (const [u, v] of edges) {
matrix[u][v] = 1;
matrix[v][u] = 1; // undirected
}
return matrix;
}
# Build adjacency matrix (undirected)
def build_matrix(edges, n):
matrix = [[0] * n for _ in range(n)]
for u, v in edges:
matrix[u][v] = 1
matrix[v][u] = 1 # undirected
return matrix
// Build adjacency matrix (undirected)
int[][] buildMatrix(int[][] edges, int n) {
int[][] matrix = new int[n][n];
for (int[] e : edges) {
matrix[e[0]][e[1]] = 1;
matrix[e[1]][e[0]] = 1; // undirected
}
return matrix;
}
When to Use Which?
Interview rule of thumb: Use adjacency list unless the problem specifically needs O(1) edge lookups. Most graph problems give us sparse graphs (way fewer edges than V²), so adjacency list saves space and is easier to traverse.
Common Input Format: Edge List
Many LeetCode problems give us edges as [[0,1], [1,2], [2,0]] plus the number of nodes n. Our first step is always converting this to an adjacency list. That’s the “build the graph” step before we do BFS/DFS.
Sometimes we get a different format — like a grid (2D array) where each cell is a node. We’ll cover grid traversal in the BFS/DFS topic.
Key Terminology
- Degree — number of edges connected to a node. In directed graphs: in-degree (incoming edges) and out-degree (outgoing edges).
- Path — sequence of nodes where each consecutive pair has an edge.
- Cycle — a path that starts and ends at the same node.
- Connected — there’s a path between every pair of nodes (undirected graphs).
- DAG — Directed Acyclic Graph. A directed graph with no cycles. Super important for topological sort.
Graphs are the foundation for everything coming next — BFS, DFS, shortest paths, topological sort. Get comfortable building adjacency lists from edge lists, because we’ll be doing it in every graph problem.