Skip to content
advancedPhase 5 · Graphs

DFS

Master depth-first search for connectivity, cycles, and path problems.

1h 15m
6 problems
Topic Progress0%

DFS Fundamentals and Implementation

How DFS Works

DFS explores as far as possible along each branch before backtracking. It uses a stack (LIFO) or recursion (call stack).

Graph:        0
             / \
            1   2
           / \\   \
          3   4   5

DFS Order (starting at 0): 0 → 1 → 3 → 4 → 2 → 5
(Goes deep first, then backtracks)

Recursive DFS

import java.util.*;

public class DFS {
    
    // Simple recursive DFS
    public void dfsRecursive(Map<Integer, List<Integer>> graph, int node, Set<Integer> visited) {
        visited.add(node);
        System.out.print(node + " ");
        
        for (int neighbor : graph.get(node)) {
            if (!visited.contains(neighbor)) {
                dfsRecursive(graph, neighbor, visited);
            }
        }
    }
    
    // DFS that returns a list
    public List<Integer> dfs(Map<Integer, List<Integer>> graph, int start) {
        List<Integer> result = new ArrayList<>();
        Set<Integer> visited = new HashSet<>();
        dfsHelper(graph, start, visited, result);
        return result;
    }
    
    private void dfsHelper(Map<Integer, List<Integer>> graph, int node, 
                          Set<Integer> visited, List<Integer> result) {
        visited.add(node);
        result.add(node);
        
        for (int neighbor : graph.get(node)) {
            if (!visited.contains(neighbor)) {
                dfsHelper(graph, neighbor, visited, result);
            }
        }
    }
}

Iterative DFS (Using Stack)

public List<Integer> dfsIterative(Map<Integer, List<Integer>> graph, int start) {
    List<Integer> result = new ArrayList<>();
    Set<Integer> visited = new HashSet<>();
    Stack<Integer> stack = new Stack<>();
    
    stack.push(start);
    
    while (!stack.isEmpty()) {
        int node = stack.pop();
        
        if (visited.contains(node)) continue;
        visited.add(node);
        result.add(node);
        
        // Push neighbors in reverse order for consistent order
        List<Integer> neighbors = graph.get(node);
        for (int i = neighbors.size() - 1; i >= 0; i--) {
            if (!visited.contains(neighbors.get(i))) {
                stack.push(neighbors.get(i));
            }
        }
    }
    
    return result;
}

DFS Template for Interview Problems

// Complete DFS template with path tracking
public boolean dfsPath(Map<Integer, List<Integer>> graph, int start, int target) {
    Set<Integer> visited = new HashSet<>();
    return dfsHelper(graph, start, target, visited);
}

private boolean dfsHelper(Map<Integer, List<Integer>> graph, int current, 
                         int target, Set<Integer> visited) {
    if (current == target) return true;
    visited.add(current);
    
    for (int neighbor : graph.get(current)) {
        if (!visited.contains(neighbor)) {
            if (dfsHelper(graph, neighbor, target, visited)) {
                return true;
            }
        }
    }
    
    return false;
}

Complexity Analysis

Operation Time Space
DFS Traversal O(V + E) O(V)
Path Finding O(V + E) O(V)
Cycle Detection O(V + E) O(V)

When to Use DFS

  • Cycle Detection: Detecting cycles in directed/undirected graphs
  • Connected Components: Counting/finding components
  • Topological Sort: Ordering with dependencies
  • Path Finding: Checking if path exists
  • Backtracking: Exploring all possibilities
  • Memoization: DFS + memoization (top-down dynamic programming)

Advanced DFS Patterns

Cycle Detection with DFS

Undirected Graph Cycle Detection

public boolean hasCycleUndirected(Map<Integer, List<Integer>> graph, int n) {
    boolean[] visited = new boolean[n];
    
    for (int i = 0; i < n; i++) {
        if (!visited[i]) {
            if (dfsCycleUndirected(graph, i, -1, visited)) {
                return true;
            }
        }
    }
    return false;
}

private boolean dfsCycleUndirected(Map<Integer, List<Integer>> graph, 
                                  int node, int parent, boolean[] visited) {
    visited[node] = true;
    
    for (int neighbor : graph.get(node)) {
        if (!visited[neighbor]) {
            if (dfsCycleUndirected(graph, neighbor, node, visited)) {
                return true;
            }
        } else if (neighbor != parent) {
            return true; // Found cycle
        }
    }
    
    return false;
}

Directed Graph Cycle Detection (3-Color Method)

public boolean hasCycleDirected(Map<Integer, List<Integer>> graph, int n) {
    int[] color = new int[n]; // 0: unvisited, 1: visiting, 2: visited
    
    for (int i = 0; i < n; i++) {
        if (color[i] == 0) {
            if (dfsCycleDirected(graph, i, color)) {
                return true;
            }
        }
    }
    return false;
}

private boolean dfsCycleDirected(Map<Integer, List<Integer>> graph, 
                                int node, int[] color) {
    color[node] = 1; // Mark as visiting
    
    for (int neighbor : graph.get(node)) {
        if (color[neighbor] == 1) {
            return true; // Back edge found - cycle!
        }
        if (color[neighbor] == 0) {
            if (dfsCycleDirected(graph, neighbor, color)) {
                return true;
            }
        }
    }
    
    color[node] = 2; // Mark as visited
    return false;
}

Number of Connected Components

public int countComponents(Map<Integer, List<Integer>> graph, int n) {
    boolean[] visited = new boolean[n];
    int components = 0;
    
    for (int i = 0; i < n; i++) {
        if (!visited[i]) {
            components++;
            dfsMarkComponent(graph, i, visited);
        }
    }
    
    return components;
}

private void dfsMarkComponent(Map<Integer, List<Integer>> graph, 
                             int node, boolean[] visited) {
    visited[node] = true;
    for (int neighbor : graph.get(node)) {
        if (!visited[neighbor]) {
            dfsMarkComponent(graph, neighbor, visited);
        }
    }
}

DFS on Grid (Flood Fill)

// Flood fill / Island counting
public void dfsGrid(int[][] grid, int r, int c, boolean[][] visited) {
    int rows = grid.length, cols = grid[0].length;
    
    if (r < 0 || r >= rows || c < 0 || c >= cols 
        || visited[r][c] || grid[r][c] == 0) {
        return;
    }
    
    visited[r][c] = true;
    
    int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
    for (int[] dir : dirs) {
        dfsGrid(grid, r + dir[0], c + dir[1], visited);
    }
}

// Count islands
public int numIslands(char[][] grid) {
    int count = 0;
    boolean[][] visited = new boolean[grid.length][grid[0].length];
    
    for (int r = 0; r < grid.length; r++) {
        for (int c = 0; c < grid[0].length; c++) {
            if (grid[r][c] == '1' && !visited[r][c]) {
                count++;
                dfsGrid(grid, r, c, visited);
            }
        }
    }
    
    return count;
}

DFS with Backtracking

// All paths from source to target
public List<List<Integer>> allPaths(Map<Integer, List<Integer>> graph, 
                                   int start, int target) {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    path.add(start);
    dfsAllPaths(graph, start, target, path, result);
    return result;
}

private void dfsAllPaths(Map<Integer, List<Integer>> graph, int current, 
                        int target, List<Integer> path, 
                        List<List<Integer>> result) {
    if (current == target) {
        result.add(new ArrayList<>(path));
        return;
    }
    
    for (int neighbor : graph.get(current)) {
        path.add(neighbor);
        dfsAllPaths(graph, neighbor, target, path, result);
        path.remove(path.size() - 1); // Backtrack
    }
}

Common DFS Problems

Problem Pattern
Number of Islands DFS on grid
Course Schedule Cycle detection
Clone Graph DFS with visited map
Binary Tree Paths DFS with backtracking
Pacific Atlantic Water Flow DFS from boundaries
/accounts-merge Connected components

Interactive Visualization

Depth-First Search (DFS) Traversal

Press Play or Step to begin
CurrentFound / DoneEliminatedUnvisited

Practice Problems

0/3solved
Number of Islands
DFS on Grid

Given a 2D grid of '1's (land) and '0's (water), count the number of islands. An island is formed by connecting adjacent lands horizontally or vertically.

Example:

Input: grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]]

Output: 1

All '1's are connected, forming one island.

Optimal Solution — O(m×n) time, O(m×n) space

DFS: mark visited cells by sinking island

class Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[0].length; c++) {
                if (grid[r][c] == '1') {
                    count++;
                    dfs(grid, r, c);
                }
            }
        }
        return count;
    }
    private void dfs(char[][] grid, int r, int c) {
        if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length || grid[r][c] == '0') return;
        grid[r][c] = '0';
        dfs(grid, r+1, c); dfs(grid, r-1, c);
        dfs(grid, r, c+1); dfs(grid, r, c-1);
    }
}

Edge Cases:

  • Empty grid
  • No land
  • All land
Clone Graph
DFS with HashMap

Given a reference of a node in a connected undirected graph, return a deep copy of the graph.

Example:

Input: adjList = [[2,4],[1,3],[2,4],[1,3]]

Output: Same structure cloned

Clone each node and its neighbors.

Optimal Solution — O(V+E) time, O(V) space

DFS with HashMap to map original to cloned nodes

class Solution {
    Map<Node, Node> map = new HashMap<>();
    public Node cloneGraph(Node node) {
        if (node == null) return null;
        if (map.containsKey(node)) return map.get(node);
        Node clone = new Node(node.val);
        map.put(node, clone);
        for (Node neighbor : node.neighbors) {
            clone.neighbors.add(cloneGraph(neighbor));
        }
        return clone;
    }
}

Edge Cases:

  • Empty graph
  • Single node
  • Disconnected components
Pacific Atlantic Water Flow
DFS - Reverse Flow

Given an m x n matrix of heights, return a list of coordinates where water can flow to both Pacific and Atlantic oceans.

Example:

Input: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5]]

Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

Water from these cells can reach both oceans.

Optimal Solution — O(m×n) time, O(m×n) space

Reverse DFS from ocean borders, find intersection

class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        int m = heights.length, n = heights[0].length;
        boolean[][] pacific = new boolean[m][n];
        boolean[][] atlantic = new boolean[m][n];
        
        for (int i = 0; i < m; i++) {
            dfs(heights, pacific, i, 0, Integer.MIN_VALUE);
            dfs(heights, atlantic, i, n-1, Integer.MIN_VALUE);
        }
        for (int j = 0; j < n; j++) {
            dfs(heights, pacific, 0, j, Integer.MIN_VALUE);
            dfs(heights, atlantic, m-1, j, Integer.MIN_VALUE);
        }
        
        List<List<Integer>> result = new ArrayList<>();
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (pacific[i][j] && atlantic[i][j])
                    result.add(Arrays.asList(i, j));
        return result;
    }
    private void dfs(int[][] h, boolean[][] visited, int r, int c, int prev) {
        if (r < 0 || r >= h.length || c < 0 || c >= h[0].length) return;
        if (visited[r][c] || h[r][c] < prev) return;
        visited[r][c] = true;
        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int[] d : dirs) dfs(h, visited, r+d[0], c+d[1], h[r][c]);
    }
}

Edge Cases:

  • Single cell
  • All same height
  • 1x1 matrix

Quiz

1. What is the key difference between DFS cycle detection in directed vs undirected graphs?

Question 1 options

2. When is DFS preferred over BFS?

Question 2 options

3. What is the primary purpose of Depth-First Search (DFS)?

Question 3 options

4. What is a common mistake when implementing Depth-First Search (DFS)?

Question 4 options

Flashcards

Question

What is the 3-color method in directed graph cycle detection?

Answer

Color 0: Unvisited. Color 1: Currently in recursion stack (visiting). Color 2: Fully processed (visited). If we reach a Color 1 node, we found a cycle (back edge).

Question

How do you modify DFS to find all paths between two nodes?

Answer

Use backtracking: add current node to path, recurse on neighbors, then remove current node (backtrack) before exploring next neighbor. Save path when target is reached.

Question

What is Depth-First Search (DFS)?

Answer

Depth-First Search (DFS) is a key concept in software engineering.

Question

When to use Depth-First Search (DFS)?

Answer

Use Depth-First Search (DFS) when building production systems that require reliability, scalability, and maintainability.

Question

Depth-First Search (DFS) best practices

Answer

Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.

Revision Notes

Key Takeaways

  • 1.DFS uses recursion or stack, explores as deep as possible first
  • 2.3-color method for directed graph cycle detection
  • 3.Track parent in undirected graph to avoid false cycle detection
  • 4.DFS + backtracking finds all paths between nodes
  • 5.For grids, use direction arrays and boundary checks

Interview Tips

  • DFS is natural for recursion problems and backtracking
  • Use 3-color method for directed graph cycles
  • For island problems, modify grid in-place to avoid extra visited array
  • DFS + memoization = top-down dynamic programming
  • Be careful of stack overflow for very deep graphs - use iterative DFS

Cheat Sheet

DFS Cheat Sheet

Recursive DFS Template

void dfs(int node, boolean[] visited) {
    visited[node] = true;
    for (int neighbor : graph.get(node)) {
        if (!visited[neighbor]) {
            dfs(neighbor, visited);
        }
    }
}

Iterative DFS Template

Stack<Integer> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
    int node = stack.pop();
    if (visited[node]) continue;
    visited[node] = true;
    for (int neighbor : graph.get(node)) {
        if (!visited[neighbor]) stack.push(neighbor);
    }
}

Cycle Detection

Undirected (with parent tracking):

boolean dfs(int node, int parent) {
    visited[node] = true;
    for (int neighbor : graph.get(node)) {
        if (!visited[neighbor]) {
            if (dfs(neighbor, node)) return true;
        } else if (neighbor != parent) {
            return true; // Cycle!
        }
    }
    return false;
}

Directed (3-color method):

// 0=unvisited, 1=visiting, 2=visited
boolean dfs(int node) {
    color[node] = 1; // Mark visiting
    for (int neighbor : graph.get(node)) {
        if (color[neighbor] == 1) return true; // Back edge!
        if (color[neighbor] == 0 && dfs(neighbor)) return true;
    }
    color[node] = 2; // Mark visited
    return false;
}

Grid DFS Directions

int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
void dfs(char[][] grid, int r, int c) {
    if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] == '0') return;
    grid[r][c] = '0'; // Mark visited
    for (int[] d : dirs) dfs(grid, r+d[0], c+d[1]);
}

Complexity

  • Time: O(V + E)
  • Space: O(V) for visited, O(H) for recursion stack (H = height)