Browse curriculum

Graphs Overview

Introduction to Graphs and adapting DFS for cycles.

Medium
10 min
Solve on LeetCode

Examples

Input:n = 5, edges = [[0,1],[1,2],[3,4]]
Output:2
Explain:

The components are {0, 1, 2} and {3, 4}.

Input:n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]
Output:1
Explain:

One chain connects all five nodes.

What is a Graph?

A Graph is a non-linear data structure consisting of vertices (nodes) and edges (lines connecting nodes). Unlike trees, graphs have no strict hierarchy: any node can be connected to any other node.

Key Terminology

  • Vertex (Node): The fundamental unit (e.g., a City, User, Webpage).
  • Edge: The connection between two vertices (e.g., Road, Follow, Hyperlink).
  • Degree: The number of edges connected to a vertex. In directed graphs, this splits into in-degree (incoming) and out-degree (outgoing).
  • Path: A sequence of edges connecting a sequence of vertices.

Real-World Examples

  1. Social Networks: Users are nodes, friendships are edges.
  2. Google Maps: Intersections are nodes, roads are edges.
  3. The Internet: Webpages are nodes, hyperlinks are edges.

Types of Graphs

Key distinctions you must know:

  • Directed (DiGraph): Edges have a direction (A → B). Moving from A to B is allowed, but B to A is not (unless a separate edge exists). e.g., Twitter followers.

  • Undirected: Edges are bidirectional (A - B). e.g., Facebook friends.

  • Weighted: Edges have values (weights) assigned to them. e.g., Flight costs between cities.

  • Cyclic vs Acyclic: A cyclic graph contains at least one path that starts and ends at the same node using different edges.

  • Connected vs Disconnected: In a connected graph, there is a path between every pair of vertices. Disconnected graphs have isolated sub-graphs (islands).

Graph Representations

How do we store a graph in code? There are two primary ways:

  1. Adjacency Matrix: A 2D array where grid[i][j] = 1 if there is an edge from i to j.

    • Pros: O(1) edge lookup.
    • Cons: O(V²) space, even if the graph is sparse.
  2. Adjacency List (Most Common): A Map or Array of Lists where each key is a node and the value is a list of its neighbors.

    • Pros: O(V + E) space. Efficient for sparse graphs.
    • Cons: O(Degree) to check if a specific edge exists.

We will focus primarily on the Adjacency List as it is the most common representation in technical interviews.

DFS on Graphs: The 'Visited' Set

DFS on a graph works just like on a tree: we explore as deep as possible before backtracking. However, there is one critical difference:

Cycles and Revisited Nodes

In a tree, we always move down from parent to child, guaranteeing we never visit the same node twice. In a graph, edges can point anywhere. A node can point back to its ancestor (cycle) or two nodes can point to the same neighbor.

Without tracking where we've been, our DFS would recurse infinitely in a cycle (A → B → A → ...).

The Solution: The visited Set
We strictly enforce a rule: "Never process a node more than once."
We maintain a HashSet (or boolean array) called visited.

  1. Before traversing to a neighbor, check if it's in visited.
  2. If yes, skip it.
  3. If no, mark it as visited and recurse.

Handling Disconnected Graphs

A single DFS from node 0 only reaches node 0's own piece of the graph. If the graph is disconnected, the other pieces — its connected components — are never touched.

So the DFS goes inside a loop over every node. A node that is still unvisited was not reached by any earlier DFS, so it starts a new component:

components = 0
for node in range(n):
    if not visited[node]:
        components += 1
        dfs(node)

Each DFS started from this loop marks exactly one whole component, so counting the starts counts the components.

DFS Graph Visualization

Count the connected components of n nodes and a list of edges (LeetCode 323). The outer loop tries every node; each one still unvisited starts a new DFS, and every node that DFS reaches gets the same component number. Watch the skips: a node already visited is never explored again, which is what stops the walk from going round a cycle forever.

Edge Cases & Common Mistakes

Watch out for these scenarios in interviews:

  • Infinite Loops: Forgetting the visited set will cause a Stack Overflow on cyclic graphs.

  • Disconnected Graphs: Assuming the graph is fully connected and only calling DFS on node 0. You must iterate through all nodes.

  • No Edges: LeetCode guarantees n ≥ 1, but edges can be empty. Then every node is its own component and the answer is n.

  • Self-Loops: A node with an edge to itself (A → A). The visited set logic handles this naturally, but be aware of it.

  • Directed vs Undirected: Treating a directed edge as undirected or vice-versa is a common bug.

Code: Count Connected Components

The template on a real problem, LeetCode 323 (Number of Connected Components in an Undirected Graph): build the adjacency list, keep a visited array, and start a DFS from every node that is still unvisited.

Note: LeetCode 323 is a Premium problem. LeetCode 547, Number of Provinces, is free and asks the same question with the graph given as an adjacency matrix instead of an edge list.

Graphs Overview Code
1var countComponents = function(n, edges) {
2 const graph = Array.from({ length: n }, () => []);
3 for (const [a, b] of edges) {
4 graph[a].push(b);
5 graph[b].push(a);
6 }
7
8 const visited = new Array(n).fill(false);
9
10 function dfs(node) {
11 visited[node] = true;
12 for (const neighbor of graph[node]) {
13 if (!visited[neighbor]) {
14 dfs(neighbor);
15 }
16 }
17 }
18
19 let components = 0;
20 for (let node = 0; node < n; node++) {
21 if (!visited[node]) {
22 components++;
23 dfs(node);
24 }
25 }
26 return components;
27};

Summary

Graph DFS is nearly identical to Tree DFS, but requires a visited set to prevent cycles and redundant work. Always verify if the graph is directed/undirected and connected/disconnected.

The approach, step by step

  1. Build the adjacency list

    Create a list for every node and add each undirected edge to both of its ends.

  2. Loop over every node

    Start a DFS from each node that is still unvisited, and count one new component.

  3. Mark and recurse

    DFS marks the node visited, then recurses into every unvisited neighbour.

Frequently asked questions

Why does DFS on a graph need a visited set?

A graph can have cycles, and two nodes can share a neighbour. Without a record of visited nodes, DFS would walk round a cycle forever or explore the same nodes again. Marking a node as soon as DFS reaches it means each node is processed once.


How do you count connected components with DFS?

Loop over every node and start a DFS from each one that is still unvisited. That DFS marks its whole component, so each start is exactly one new component.


Should I use an adjacency list or an adjacency matrix?

An adjacency list uses O(V + E) memory and lists a node’s neighbours directly, which suits the sparse graphs in most interview problems. A matrix uses O(V²) memory but answers whether two nodes are joined in O(1).

Applying for software engineering roles? Free, no sign-up:

AI resume checker — match your resume to the job description