Skip to content
DSA Patterns

Learn

Graphs

The graph algorithms interviews actually ask for: topological sort for dependencies, Dijkstra for weighted shortest paths, union-find for connectivity, and Prim/Kruskal for spanning trees.

4 min readUpdated 2 Sept 2026

#Graphs#Topological Sort#Dijkstra#Union-Find

Beyond plain BFS and DFS, four named algorithms cover essentially every graph question asked in interviews. The skill being tested is picking the right one from the problem statement.

Choosing

The question saysUse
Prerequisites, ordering, "can this be scheduled"Topological sort
Shortest path, weighted edgesDijkstra
Shortest path, unweightedBFS
Are these connected / merge groupsUnion-Find
Connect everything at minimum total costMST (Prim or Kruskal)
Shortest path with negative weights, or ≤ k edgesBellman-Ford

Topological sort

An ordering where every edge points forward. Only exists on a DAG — so the same algorithm that produces the order also detects cycles, which is why Course Schedule and Course Schedule II are the same problem.

Kahn's algorithm (BFS over in-degrees) is the one to write:

from collections import deque

def topo_sort(n, edges):
    adj = [[] for _ in range(n)]
    indegree = [0] * n
    for u, v in edges:                 # u must come before v
        adj[u].append(v)
        indegree[v] += 1

    queue = deque(i for i in range(n) if indegree[i] == 0)
    order = []

    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in adj[node]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:     # every prerequisite satisfied
                queue.append(nxt)

    return order if len(order) == n else []     # short = there is a cycle

The length check is the cycle detection: nodes inside a cycle never reach in-degree zero, so they never enter the queue.

Alien Dictionary is the same algorithm with the graph hidden — compare adjacent words,

and the first differing character gives one edge. Watch the invalid-input case where a word is a prefix of a shorter predecessor.

Dijkstra

BFS with a priority queue instead of a plain one, so the cheapest frontier node is expanded first rather than the nearest by hop count.

import heapq

def dijkstra(n, adj, source):
    dist = [float("inf")] * n
    dist[source] = 0
    heap = [(0, source)]

    while heap:
        d, node = heapq.heappop(heap)
        if d > dist[node]:
            continue                    # stale entry, already improved
        for nxt, weight in adj[node]:
            if d + weight < dist[nxt]:
                dist[nxt] = d + weight
                heapq.heappush(heap, (dist[nxt], nxt))

    return dist

The d > dist[node] skip is the lazy-deletion idiom: heaps cannot update a key, so you push a better entry and ignore the outdated one when it surfaces.

Dijkstra requires non-negative weights. With negatives, a shorter path can appear after a node is finalised — that is Bellman-Ford's territory. Cheapest Flights Within K Stops is exactly that case: the extra "at most k edges" constraint makes plain Dijkstra unsound, and Bellman-Ford relaxing k+1 times is the clean answer.

Union-Find

Near-constant-time "are these connected" and "merge these groups", with both optimisations:

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]   # path compression
            x = self.parent[x]
        return x

    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False                                   # already together
        if self.rank[ra] < self.rank[rb]:
            ra, rb = rb, ra
        self.parent[rb] = ra                               # union by rank
        self.rank[ra] += self.rank[ra] == self.rank[rb]
        return True

Path compression plus union by rank gives O(α(n)) amortised — effectively constant.

union returning False is the useful part: it means the two nodes were already connected, so adding this edge creates a cycle. Number of Connected Components is n minus the number of successful unions; Graph Valid Tree is "exactly n - 1 edges and no union ever fails".

Minimum spanning tree

Min Cost to Connect All Points — connect every node at the lowest total cost.

  • Kruskal: sort all edges by weight, add each one whose endpoints are not already

connected (that is union-find's False again). O(E log E).

  • Prim: grow one tree, repeatedly taking the cheapest edge leaving it, via a min-heap.

O(E log V), and better on dense graphs — a complete graph of points, for instance, where materialising all E edges for Kruskal is wasteful.

Complexity

Topological sort O(V + E). Dijkstra O(E log V). Union-find O(α(n)) per operation. Kruskal O(E log E), Prim O(E log V). Bellman-Ford O(V × E). Building the adjacency list is O(V + E) and is worth doing explicitly rather than scanning an edge list repeatedly.

Mistakes that cost the round

  • Reversing edge direction in topological sort. u → v means "u before v", so v's

in-degree increases.

  • Dijkstra with negative weights. It is simply wrong, not just slow.
  • Skipping the stale-entry check, which re-expands nodes and can turn into a timeout.
  • Union-find without path compression, degrading to O(n) per find.
  • Forgetting the cycle check. Course Schedule is unsolvable, not merely unordered,

when a cycle exists — the length comparison is the answer, not an afterthought.

What to drill

  1. Course ScheduleCourse Schedule II — cycle detection, then the order.
  2. Number of Connected Components — union-find, or DFS.
  3. Graph Valid Tree — connectivity plus the edge count.
  4. Network Delay Time — Dijkstra, plainly.
  5. Cheapest Flights Within K Stops — Bellman-Ford with a hop limit.
  6. Min Cost to Connect All Points — Prim on a dense graph.
  7. Alien Dictionary — build the graph, then topologically sort it.
  8. Word Ladder II — BFS for the distances, then backtrack every shortest path.

All on the 22 DSA Patterns sheet.

Frequently asked

How does topological sort detect a cycle?

Kahn's algorithm only enqueues a node once its in-degree reaches zero, which requires every prerequisite to have been processed. Nodes inside a cycle can never reach zero, so they never enter the queue. If the produced order is shorter than the node count, the leftover nodes form a cycle.

When can't I use Dijkstra?

When any edge weight is negative — Dijkstra finalises a node's distance when it is popped, and a negative edge can improve it later, making the result wrong. Use Bellman-Ford instead. Bellman-Ford is also the right choice when the path is constrained to at most k edges, as in Cheapest Flights Within K Stops.

What does union-find give you that DFS does not?

Incremental connectivity. Union-find answers 'are these two connected' in near-constant time while edges are still being added, which DFS cannot do without re-traversing. It also detects cycles for free — a union whose endpoints already share a root means the new edge closes a loop — and that is the core of Kruskal's MST algorithm.

Related