AlgoMaster Logo

Find Eventual Safe States

Last Updated: April 5, 2026

medium
4 min read

Understanding the Problem

We have a directed graph, and we need to figure out which nodes are "safe." A node is safe if no matter which path you take from it, you always end up at a terminal node, one with zero outgoing edges. The moment any path from a node can reach a cycle, that node becomes unsafe.

Think about it this way: if you're standing at a node and you follow edges randomly, can you ever get stuck in an infinite loop? If the answer is yes for even one possible path, that node is not safe. Terminal nodes are trivially safe since there's nowhere to go. And nodes that only point to safe nodes are also safe, because every path from them eventually terminates.

The core insight is that unsafe nodes are exactly those that lie on a cycle or can reach a cycle. So this problem boils down to: find all nodes that cannot reach any cycle in the graph.

Key Constraints:

  • n <= 10^4 -> We can afford O(n^2) at most, but O(n + E) is ideal. This rules out all-pairs approaches but keeps single-pass graph traversals on the table.
  • edges <= 4 * 10^4 -> The graph is relatively sparse. Combined with n up to 10^4, we need algorithms that are linear in V + E.
  • The graph may contain self-loops -> A self-loop (node pointing to itself) immediately makes that node unsafe. Our solution needs to handle this naturally.

Approach 1: DFS with Three-Color Marking

Intuition

The first thing that comes to mind when we hear "cycle detection in a directed graph" is DFS with coloring. The classic approach uses three colors (or states):

  • WHITE (0): The node hasn't been visited yet.
  • GRAY (1): The node is currently on the DFS recursion stack, meaning we're in the middle of exploring paths from it.
  • BLACK (2): The node has been fully processed, and we know its final status.

Here's the key idea: if during DFS from node u, we reach a GRAY node, we've found a cycle. That means u (and every node on the path back to that GRAY node) is unsafe. But if all paths from u lead to BLACK nodes that were marked safe, then u is safe too.

We can refine our coloring slightly. Instead of just BLACK, we track whether a fully processed node is SAFE or UNSAFE. When we finish exploring all neighbors of a node:

  • If any neighbor is UNSAFE or led to a cycle (was GRAY when we found it), mark the current node UNSAFE.
  • If all neighbors are SAFE, mark the current node SAFE.

This way, each node is visited at most once, and we get our answer in a single DFS pass over the entire graph.

Algorithm

  1. Create a color array of size n, initialized to WHITE (0) for all nodes.
  2. For each node i from 0 to n-1, if it hasn't been visited, run DFS on it.
  3. In the DFS for node u:
    • Mark u as GRAY (visiting).
    • For each neighbor v of u:
      • If v is GRAY, we found a cycle. Return false (unsafe).
      • If v is WHITE, recursively DFS on v. If it returns false, return false.
      • If v is already marked SAFE, skip it. If it's marked UNSAFE, return false.
    • If all neighbors are safe, mark u as SAFE (2) and return true.
    • If any neighbor was unsafe, mark u as UNSAFE (3) and return false.
  4. Collect all nodes marked SAFE into the result list.

Example Walkthrough

1Initial graph. Start DFS from node 0, mark it GRAY
0GRAY123546
1/7

Code

The DFS approach is already O(V + E), which is optimal. But recursive DFS has a practical weakness: for very deep graphs, the recursion stack can overflow. The next approach solves the same problem iteratively using a completely different perspective.

Approach 2: Topological Sort on Reverse Graph

Intuition

Let's flip the problem on its head. Instead of asking "which nodes can reach a cycle?", let's ask "which nodes can be reached from terminal nodes when we reverse all edges?"

Here's the reasoning:

  • Terminal nodes (no outgoing edges) are definitely safe.
  • In the original graph, a node is safe if ALL of its outgoing neighbors are safe.
  • If we reverse the graph, a terminal node in the original becomes a node with zero in-degree in the reversed graph.

So the algorithm works on the original graph's out-degree. We track each node's out-degree. When a neighbor is confirmed safe, we decrement the out-degree. Once a node's out-degree reaches 0 (all neighbors are safe), we add it to the queue and mark it safe. This is essentially Kahn's algorithm applied to out-degree in the reverse graph.

Algorithm

  1. Build the reverse graph: for each edge u -> v in the original graph, add edge v -> u.
  2. Compute the out-degree of each node in the original graph (this is just graph[i].length).
  3. Initialize a queue with all terminal nodes (out-degree 0).
  4. BFS: for each node dequeued (confirmed safe), look at its reverse neighbors. Decrement their out-degree. If a node's out-degree reaches 0, add it to the queue.
  5. All nodes that were ever added to the queue are safe. Sort and return them.

Example Walkthrough

1Out-degrees: 0→2, 1→2, 2→1, 3→1, 4→1, 5→0, 6→0. Queue: [5, 6]
01235queue46queue
1/6

Code