What is a Graph?
At its core, a graph is extremely simple. A graph is just a collection of dots (called Vertices or Nodes) and lines (called Edges) that connect them. Unlike a Tree (which has a single root node at the top and spreads downward), a Graph has no hierarchy, no roots, and can form loops or cycles.
Directed vs. Undirected
• Undirected: Edges are two-way. Connections are mutual (like Facebook friendships).
• Directed: Edges have arrows. Flow goes one way (like Twitter followers or class prerequisites).
Weighted vs. Unweighted
• Unweighted: All lines are equal. Moving between nodes always costs 1 step.
• Weighted: Connections have costs (like mileage between cities or server response latencies).
How to Represent Graphs in Code
Since a graph has no single "root" pointer, how do we pass it to functions? Switch tabs in the interactive visualizer below to see the three main ways programmers store graph relations in code:
Graph Representations
Graphs have no root nodes. We store them by cataloging connections. Hover over any node in the SVG to see which edges light up, then click the tabs above to inspect how the relationships are stored in variables!
Graph Traversals: DFS vs. BFS
Once a graph is stored, how do we navigate it? We have two primary search engines: DFS and BFS. Unlike trees (which always expand downward and can never loop), graphs can contain cycles. This introduces the most important rule in Graph algorithms: You must maintain a visited tracker to avoid infinite loops.
DFS: Depth Exploration
Plunges down a single path as far as possible before backtracking.
Ideal for: Finding path existence, traversing cycles, or checking if two items are connected.
BFS: Layered Expansion
Explores the graph layer-by-layer like ripples expanding in water.
Ideal for: Shortest path on an unweighted structure, finding the minimum steps.
THE VISITED SET TRAP
Without a visited set, cyclic graph searches cause stack overflows (DFS) or memory exhaustion (BFS) from spinning in loops. In BFS, always mark nodes visited as soon as you push them onto the queue, not when you pop them!
Cycle Detection (Finding Loops)
Why it Matters
Cycles create infinite loops, locking algorithms or causing compilation circular dependencies (e.g., File A imports B, B imports A).
When to Use
• Detect grid/maze deadlocks.
• Verify circular course scheduling dependencies.
• Confirm if a structure is a valid tree (no cycles).
How to Code
Maintain a currentPath recursion stack tracking nodes in the current DFS path. If we revisit a node that is currently in our traversal route, we have a cycle!
This is the same idea as the 3-color version on the DFS page — currentPath is GRAY, and visited without currentPath is BLACK.
Topological Sort (Ordering DAGs)
Why it Matters
If tasks have prerequisites (like university courses), you need a linear sequence ordering them so you never attempt a task before completing its requirements.
When to Use
• Finding a compiler file-build schedule.
• Creating a valid class graduation roadmap.
• Modeling package dependency trees.
How to Code (Kahn's BFS)
Track each node's incoming connections (inDegree). Put all nodes with inDegree = 0 (no prerequisites) in a queue. As we pop them, decrement neighbors' inDegree. Push any that hit 0.
Disjoint Set Union (DSU / Union-Find)
Why it Matters
Queries connection queries (are A and B connected?) in near constant amortized time ($O(\alpha(N))$) without conducting complete BFS or DFS traversals.
When to Use
• Dynamic connection updates (real-time friends adding).
• Kruskal's Minimum Spanning Tree algorithm.
• Finding redundant connections in undirected networks.
How to Code
Treat groups as trees where each element points to a parent. Use find(x) with Path Compression to locate the group leader. Connect trees with Union-by-Rank to maintain shallow depths.
Shortest Path Algorithms
Why it Matters
Finding the minimum-cost route between points on weighted connection maps.
When to Use
• Unweighted Graphs: Use simple BFS queue.
• Weighted Graphs (positive): Use Dijkstra's algorithm.
• Negative Weights: Use Bellman-Ford.
How to Code (Dijkstra)
Use a Min-Priority Queue mapping nodes sorted by current distance. Always expand the lowest-cost node, updating adjacent distances and pushing nodes.
Connected Components (Multi-Start searches)
Why it Matters
Graphs are often divided into disjoint, isolated components. Standard single-start searches will fail to scan everything.
When to Use
• Count "Number of Islands" on grid matrices.
• Identify isolated, disjoint clusters of social users.
How to Code
Loop from 0 to V-1. If a node is unvisited, we increment the cluster count and launch a DFS to traverse and mark all nodes in that cluster.
Common Mistakes
Forgetting Visited
Without a visited set, any graph with a cycle spins forever — stack overflow in DFS, unbounded queue growth in BFS. Always track it, and for BFS mark visited the moment a node is pushed, not when it's popped.
Directed vs Undirected Adjacency
Building an adjacency list with only adj[a].push(b) makes a directed edge a→b. If the graph is actually undirected, you must also add adj[b].push(a) — forgetting this silently turns half your edges one-way.
1-Indexed Nodes, 0-Indexed Arrays
Many problems label nodes 1..N, but arrays (visited, adj) are naturally 0..N-1. Either size arrays to N+1 and ignore index 0, or subtract 1 consistently everywhere — mixing the two is a classic off-by-one source.
Ready to Practice?
"A graph is just nodes that know their neighbors. Trace your links, remember where you've been, and never get trapped in a cycle."