Home › DSA Patterns › Topological Sort
T

Topological Sort

Learn this one pattern well and a whole class of "ordering with dependencies" problems becomes easy. They show up constantly, and most candidates lose the round in one of two ways: they miss the signal and grab the wrong tool, or they get the sort right and skip the cycle check. The second one is a silent killer: you hand back a confident, wrong answer and only find out when the interviewer asks "what if the input has a cycle?"

DSA PatternsMedium11 min read
Who asks this, and how oftenInterview frequency: High

Most asked at Amazon. Here is how often this pattern shows up in each company's coding rounds.

Amazon
84
Google
82
Meta
78
Microsoft
72
Uber
66
Relative frequency index from 0 to 100, based on commonly reported interview experiences. Use it as a guide to where this pattern pays off most, not an exact count.

If you remember five things

This is the whole pattern in one glance. Everything below defends these five.

  1. It is a DAG ordering. Nodes are tasks, an edge A to B means "A before B," and a valid order exists only if the graph has no cycle.
  2. Reach for Kahn's. BFS on in-degrees: repeatedly take a zero in-degree node, remove its out-edges, free whatever drops to zero. It detects cycles for free.
  3. The length check is the cycle check. If len(order) != num_nodes, the leftover nodes sit in a cycle. Never skip this line.
  4. Edge direction is where points leak. A pair that reads "b before a" means the arrow points from b to a. Decide it out loud before you code.
  5. Seed the queue with every zero. Scan all nodes, not just the first. Disconnected pieces and isolated nodes break any solution that starts from one node.

1The mental model (what this really is)#

Topological sort answers one plain question: given tasks where some must happen before others, what order can I do them in?

  • Think about courses. You cannot take Algorithms before Intro to Programming, so Intro comes first.
  • Some courses have no relationship at all, so their order does not matter.
  • A topological sort is any ordering that respects every "X before Y" rule, and nothing more.

The structure underneath is a directed graph.

  • Each node is a task. An edge from A to B means "A must come before B."
  • The whole thing only makes sense if the graph is a DAG, a Directed Acyclic Graph.
  • Acyclic is the keyword. If A depends on B and B depends on A, no valid order exists: each waits on the other forever. That is a cycle.
  • In practice the sort itself is the easy part. The points live in the cycle check and in getting edge direction right.

Burn this into memory: there is usually more than one correct answer.

  • If three tasks have no dependencies between them, any of the 6 orderings of those three is fine.
  • So "return a valid ordering" is not asking for a unique answer. Do not waste time hunting for one.
  • If they want a specific one (smallest lexicographic order), that is a variation, handled in section 5.
The one idea behind everything

A node with zero unmet prerequisites is safe to do right now. Do it, remove its outgoing edges, and that may free the next node. The entire algorithm is just repeating that until nothing is left or you get stuck (which means a cycle).

2How to recognize it (the signals in a problem)#

You should feel the pull toward topological sort the moment you see these words:

  • "prerequisites," "dependencies," "before," "after," "must come first"
  • "ordering" or "sequence" where the order is constrained by rules
  • "Can you finish all tasks?" or "Is there a valid schedule?" (this is really "is the graph acyclic?")
  • Build systems, task schedulers, course schedules, package installs, recipe steps
  • Any input that hands you pairs like [a, b] meaning a one-way relationship between two items

The tell that separates this from a plain graph traversal:

  • The problem cares about an order that respects direction.
  • Not "can I reach this node," but "what must I finish before I am allowed to start this one."

When it does NOT apply:

  • Mutual relationships (friendship, two-way roads) are an undirected graph, and topological sort does not fit.
  • "Ordering" means nothing without direction. Edges must point one way and carry a meaning of precedence.
  • If you cannot say which of two related items comes first, this is not your pattern.

3The template (how it works, with pseudocode)#

Two standard approaches exist. Learn Kahn's algorithm (BFS with in-degrees) first.

  • It is the one I reach for in interviews: it detects cycles for free and the logic is hard to get wrong under pressure.
  • The DFS version needs a separate three-state marking just to catch cycles, and that extra bookkeeping is where people slip.

The idea behind Kahn's:

  • A node with zero incoming edges has no unmet prerequisites, so it is safe to do now.
  • Do it, remove its outgoing edges, and that may drop another node to zero and free it.
  • Repeat until nothing is left.
function topo_sort(num_nodes, edges):
    # Build adjacency list and in-degree count
    graph = [[] for _ in range(num_nodes)]
    in_degree = [0] * num_nodes
    for (a, b) in edges:          # a must come before b
        graph[a].append(b)
        in_degree[b] += 1

    # Start with every node that has no prerequisites
    queue = [n for n in range(num_nodes) if in_degree[n] == 0]
    order = []

    while queue is not empty:
        node = queue.pop_front()
        order.append(node)
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1   # we "consumed" one prerequisite
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    # If we couldn't place every node, there's a cycle
    if len(order) == num_nodes:
        return order
    else:
        return []     # or raise "cycle detected"

That last check is the whole game.

  • If a cycle exists, the nodes inside it never hit in-degree zero, so they never enter the queue, and order comes up short.
  • One comparison, len(order) == num_nodes, tells you the graph is invalid.
  • Do not skip it, and say out loud why it works: cyclic nodes cannot fit.

The DFS version is worth knowing so you can name it:

  • Run a depth-first search, and after you finish exploring all descendants of a node, push it onto a stack. Reverse the stack at the end for your order.
  • Cycle detection needs a three-color marking (unvisited, in-progress, done). Reach an in-progress node and you have looped back on yourself, which is a cycle.
  • It works, but under stress the in-progress tracking trips people up, and a missing color check silently returns a wrong order. I default to Kahn's.
From the interviewer's chair

When a candidate writes Kahn's and narrates "if there is a cycle these nodes never reach in-degree zero, so the length comes up short," I relax. That one sentence tells me they understand why the check works, not that they memorized a line. If the check appears with no explanation, I will ask you to prove it, on the spot.

4A worked example (walk through one problem step by step, in your own words)#

The classic: Course Schedule II.

  • You have numCourses courses labeled 0 to n minus 1, plus a list of prerequisite pairs.
  • Each pair [a, b] means you must take course b before course a.
  • Return any valid order to take all courses, or an empty list if it is impossible.

Let's use numCourses = 4 and prerequisites = [[1,0], [2,0], [3,1], [3,2]].

First, read the edges carefully, because this is exactly where points leak.

  • The pair [1,0] means "take 0 before 1," so the edge goes from 0 to 1.
  • The arrow points from the prerequisite to the course that needs it.
  • Translate all four, slowly: [1,0] gives 0 to 1, [2,0] gives 0 to 2, [3,1] gives 1 to 3, [3,2] gives 2 to 3.

Now build in-degrees (how many prerequisites each course has):

  • Course 0: nobody points to it. In-degree 0.
  • Course 1: course 0 points to it. In-degree 1.
  • Course 2: course 0 points to it. In-degree 1.
  • Course 3: courses 1 and 2 point to it. In-degree 2.

Here's the shape of the graph:

graph TD C0[Course 0] --> C1[Course 1] C0 --> C2[Course 2] C1 --> C3[Course 3] C2 --> C3
Notice that Course 0 is the only node with nothing pointing at it, so it is the single entry into the queue. Course 3 sits at the bottom with two arrows in, so it cannot move until both 1 and 2 are done. The diamond shape is why the answer is not unique.
Pause before you read the trace: after you process course 0 and free courses 1 and 2, when exactly is course 3 allowed into the queue?
Reveal the answer
Only after both 1 and 2 have been processed. Course 3 starts at in-degree 2. Processing 1 drops it to 1 (still blocked), and processing 2 drops it to 0 (now free). Pushing 3 the moment you touch it from course 1 is the classic bug: it violates the course-2 prerequisite you have not satisfied yet.

Now trace it.

  • Start the queue with everything at in-degree 0. That is just [0].
  • Process 0. order = [0]. Neighbors are 1 and 2. Drop each one's in-degree by one. Course 1 goes 1 to 0, free, push it. Course 2 goes 1 to 0, free, push it. Queue is now [1, 2].
  • Process 1. order = [0, 1]. The only neighbor of 1 is 3. Course 3 drops from 2 to 1. Not zero, so it waits. Do not push 3 yet. Queue is [2].
  • Process 2. order = [0, 1, 2]. Neighbor of 2 is 3. Course 3 drops from 1 to 0. Now truly free, push it. Queue is [3].
  • Process 3. order = [0, 1, 2, 3]. No neighbors. Queue is empty.

The result:

  • We placed all 4 courses, so len(order) == 4 == numCourses and the answer is [0, 1, 2, 3].
  • Note that [0, 2, 1, 3] would have been equally correct if the queue had handed us 2 before 1.
  • Both respect every rule. That is the "more than one answer" point from section 1, made concrete.

Now break it on purpose.

  • Add the pair [0,3], meaning take 3 before 0, edge 3 to 0.
  • That creates a loop: 0 needs 3, and 3 needs 1 and 2, which need 0.
  • Every course in that tangle now has in-degree at least 1, so the initial queue is empty, nothing gets processed, order stays empty, and len(order) = 0 != 4.
  • We return []. Impossible schedule, correctly caught. That is the exact moment the cycle check earns its keep.

5Variations and gotchas#

Just checking feasibility.

  • Some problems ("Course Schedule I") only ask "can you finish all tasks?" You return a boolean, not the order.
  • Run Kahn's and return whether you placed every node (len(order) == num_nodes).
  • Same algorithm, simpler return.

Lexicographically smallest order.

  • When the problem wants a specific tie-break (smallest number first, alphabetical), swap the plain queue for a min-heap.
  • Instead of processing zero in-degree nodes in arbitrary order, always pop the smallest available one.
  • That single swap handles it, and it costs you a log factor (see section 6).

Alien Dictionary style.

  • You are given words sorted in some unknown alphabet and must recover the letter order.
  • Turn it into a graph first: compare each pair of adjacent words, find the first differing character, and that one difference gives you a single ordering edge.
  • Then topological sort the letters. The hard part is building the graph, not the sort.
  • Watch the edge case where a longer word comes before its own prefix (like "abc" before "ab"), which is invalid input and should return "" rather than a bogus order.

Counting all orderings.

  • Rare in interviews. If asked how many valid orderings exist, that is a harder combinatorial problem, not a simple topo sort.
  • Name it as such and do not pretend one Kahn's pass answers it.

The disconnected graph trap.

  • Your graph may have several separate pieces, or isolated nodes with no edges at all.
  • An isolated node has in-degree 0, so it belongs in the initial queue.
  • Start BFS from only one node and you silently drop every other component.
  • Initialize the queue by scanning all nodes for in-degree zero, not just the first one you find.

6Complexity#

Time: O(V + E).

  • V is the number of nodes, E the number of edges. You touch each node once and each edge once.
  • That is optimal: you cannot order the graph without at least looking at every node and every dependency.

Space: O(V + E).

  • The adjacency list holds all E edges, and the in-degree array plus the queue hold up to V entries.

The lexicographic variation: O(V log V + E).

  • Each of the V heap operations is logarithmic.
  • Still fast, just slightly more than linear, and worth stating explicitly if you reach for the heap.

7Mistakes that cost people the round#

Reversing the edge direction.

  • This is the number one killer.
  • When a pair means "b before a," decide once, out loud, which way the arrow points, and write it on the board before you touch code.
  • Get this backward and your entire sort is inverted, often unnoticed until the sample test fails and your time is gone.

Forgetting cycle detection.

  • Candidates return order without checking its length against the node count.
  • If the graph has a cycle, you hand back a partial, wrong answer and claim it is valid.
  • Always verify len(order) == num_nodes, and narrate why: "if there is a cycle, those nodes never reach in-degree zero, so they will not fit, and the length tells me."

Only seeding the queue with one node.

  • Disconnected components and isolated nodes break naive solutions.
  • Scan all nodes for in-degree zero at the start, every time.

Mutating in-degree wrong.

  • Decrement the neighbor's in-degree, and only push it when it hits exactly zero.
  • Push it the moment you touch it, before it is truly free, and you produce garbage orderings that violate a prerequisite you have not satisfied yet.
  • One off-by-one here and you are done.

Not clarifying uniqueness.

  • Confirm early whether any valid order is acceptable or they want a specific one (smallest, alphabetical).
  • Thirty seconds of asking saves you from solving the wrong problem and from a heap you did not need.

Treating an undirected graph as topo-sortable.

  • If edges have no direction or precedence meaning, this pattern does not fit.
  • Make sure the dependency is actually one-way before you start building in-degrees.
Red flag that fails you here

Returning order with no length check, then saying "that is my answer" on an input I seeded with a cycle. The moment I ask "what if there is a cycle?" and you pause, I know the check was not in your mental model. Put the len(order) == num_nodes line in every time, even for feasibility-only problems, and say why it works.

8Your practice path (named problems by difficulty)#

Do them in this order. Each one adds exactly one layer on the last.

Warm-up and core:

  • Course Schedule (feasibility only, learn the cycle check in isolation)
  • Course Schedule II (return the order, the canonical problem, the one from section 4)
  • Find Eventual Safe States (cycle reasoning, reverse thinking)

Building the graph is the real work:

  • Minimum Height Trees (topo-sort-like peeling from the leaves inward)
  • Alien Dictionary (construct edges from word comparisons, then sort)
  • Sequence Reconstruction (verify a unique ordering exists, exactly one zero in-degree node at each step)

Harder combinations:

  • Parallel Courses (topo sort plus level counting for minimum semesters)
  • Course Schedule IV (reachability queries layered on top of ordering)
  • Sort Items by Groups Respecting Dependencies (two-level topo sort, groups and items)
  • Build a Matrix With Conditions (topo sort on both rows and columns)

The bar:

  • Finish "Sort Items by Groups Respecting Dependencies" cleanly, with correct cycle handling at both levels, and you understand this pattern at an interview level.

9Where this shows up (which companies/rounds lean on it)#

It is a favorite at companies with real dependency systems:

  • Build tooling, package managers, data pipelines, and scheduling.
  • Google and Meta ask the course-schedule family often in the graph portion of an onsite.
  • Amazon leans toward the dependency and build-order framing, which maps straight onto how their internal build systems work.
  • It also folds into system design discussions about task orchestration and job scheduling, where the interviewer wants you to name the DAG out loud and explain how you would detect cycles in a pipeline before it deadlocks in production.

It is a medium topic that rewards precision.

  • The algorithm is short, maybe 15 lines.
  • The points are won or lost on three things: edge direction, cycle detection, and seeding the queue with every zero in-degree node.
  • Nail those three and this becomes one of the most reliable problems you can get.

The sort is 15 lines of muscle memory. The round is won on edge direction, the cycle check, and seeding every zero.

Follow-ups the interviewer will ask (and your answers)
"What if the input has a cycle?"
The nodes inside the cycle never reach in-degree zero, so they never enter the queue and `order` comes up short. I check `len(order) == num_nodes`: if it fails, there is a cycle and I return `[]` (or whatever the problem wants for impossible). The check is not optional, it is the only thing distinguishing a valid DAG from an invalid graph.
"Can you do it with DFS instead of Kahn's?"
Yes. Run DFS, and after finishing all descendants of a node, push it onto a stack, then reverse the stack for the order. For cycle detection I use three-color marking: unvisited, in-progress, done. Hitting an in-progress node means I looped back on myself, which is a cycle. It works, but the in-progress tracking is easy to get wrong under pressure, so I default to Kahn's unless asked.
"The problem wants the lexicographically smallest valid order. What changes?"
Swap the FIFO queue for a min-heap. Instead of popping zero in-degree nodes in arbitrary order, always pop the smallest available one. Everything else is identical. The cost goes from O(V + E) to O(V log V + E) because each of the V pops is now logarithmic.
"Your graph has two disconnected pieces. Does your code still work?"
Yes, as long as I seed the queue by scanning every node for in-degree zero, not just starting from one node. Each component contributes its own zero in-degree starts, and isolated nodes (in-degree zero, no edges) go straight into the queue and out into the order. A single-start BFS would silently drop every other component.
"How do you know there is exactly one valid order?"
A unique topological order exists only if, at every step of Kahn's, the queue holds exactly one node. If two or more nodes are ever free at once, their relative order is unconstrained and multiple valid orders exist. That is the exact check Sequence Reconstruction asks for: run Kahn's and assert the queue never exceeds size one.

What you can now explain in the room

  • Why topological sort needs a DAG, and what a cycle means for the answer.
  • Kahn's algorithm end to end, and why the length check is the cycle check.
  • How to read a prerequisite pair and set the edge direction correctly, out loud.
  • Why you seed the queue with every zero in-degree node, not just one.
  • The min-heap swap for a lexicographically smallest order, and its log-factor cost.
  • When this pattern does not apply (undirected or non-precedence edges) and how to say so.

Want to know which rounds at a specific company lean on graph patterns like this? Check our company guides to see where topological sort shows up.

Get a prep plan built around your Topological Sort interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Topological Sort prep plan is on the way.