/

DFS (Depth-First Search)

Depth-First Search dives as deep as possible along each branch before backtracking. It uses a stack (LIFO) — either explicitly or via recursion's call stack — to track which node to explore next. DFS is the foundation for cycle detection, topological sorting, and many other graph algorithms.

Searching O(V+E)

How DFS Works

DFS starts at a chosen source node and explores as far as possible along each branch before backtracking. Unlike BFS, which explores level by level, DFS goes deep first.

The iterative algorithm proceeds as follows:

  • Push the starting node onto the stack.
  • While the stack is not empty, pop the top node.
  • If the popped node has not been visited, mark it as visited.
  • For each unvisited neighbor, push it onto the stack.
  • Repeat until the stack is empty.

The recursive version replaces the explicit stack with the call stack: for each unvisited neighbor of the current node, recursively call DFS. The base case is when a node has no unvisited neighbors.

Key insight: Because DFS explores one entire branch before moving to the next, it is well-suited for problems that require exhaustive exploration, such as finding connected components, detecting cycles, computing strongly connected components (Tarjan's/Kosaraju's), and generating topological orderings of directed acyclic graphs (DAGs).

Interactive Visualization

Watch DFS traverse the same graph used in the BFS topic. White nodes are unvisited, yellow nodes are on the stack, and green nodes have been fully visited. The stack contents are shown vertically on the right.

Click Start to begin DFS traversal from node 0.
Speed No steps

Code Implementation

Below are both the recursive and iterative implementations of DFS. The recursive version is more concise, while the iterative version avoids stack overflow on very deep graphs.

def dfs_recursive(graph, node, visited=None):
    """Perform DFS recursively from the given node."""
    if visited is None:
        visited = set()
    visited.add(node)
    print(node, end=" ")

    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs_recursive(graph, neighbor, visited)

    return visited


def dfs_iterative(graph, start):
    """Perform DFS iteratively using an explicit stack."""
    visited = set()
    stack = [start]
    order = []

    while stack:
        node = stack.pop()          # Pop from top
        if node in visited:
            continue
        visited.add(node)
        order.append(node)

        # Push neighbors in reverse order so first neighbor is on top
        for neighbor in reversed(graph[node]):
            if neighbor not in visited:
                stack.append(neighbor)

    return order


# Example usage
graph = {
    0: [1, 2],
    1: [0, 3, 4],
    2: [0, 5, 6],
    3: [1],
    4: [1],
    5: [2],
    6: [2]
}
print(dfs_iterative(graph, 0))  # Output: [0, 1, 3, 4, 2, 5, 6]
import java.util.*;

public class DFS {
    /** Recursive DFS */
    public static 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);
            }
        }
    }

    /** Iterative DFS using an explicit stack */
    public static List<Integer> dfsIterative(Map<Integer, List<Integer>> graph,
                                               int start) {
        Set<Integer> visited = new HashSet<>();
        Deque<Integer> stack = new ArrayDeque<>();
        List<Integer> order = new ArrayList<>();

        stack.push(start);

        while (!stack.isEmpty()) {
            int node = stack.pop();     // Pop from top
            if (visited.contains(node)) continue;

            visited.add(node);
            order.add(node);

            List<Integer> neighbors = graph.get(node);
            // Push in reverse so first neighbor is on top
            for (int i = neighbors.size() - 1; i >= 0; i--) {
                if (!visited.contains(neighbors.get(i))) {
                    stack.push(neighbors.get(i));
                }
            }
        }

        return order;
    }

    public static void main(String[] args) {
        Map<Integer, List<Integer>> graph = new HashMap<>();
        graph.put(0, Arrays.asList(1, 2));
        graph.put(1, Arrays.asList(0, 3, 4));
        graph.put(2, Arrays.asList(0, 5, 6));
        graph.put(3, Arrays.asList(1));
        graph.put(4, Arrays.asList(1));
        graph.put(5, Arrays.asList(2));
        graph.put(6, Arrays.asList(2));

        System.out.println(dfsIterative(graph, 0));
        // Output: [0, 1, 3, 4, 2, 5, 6]
    }
}
#include <iostream>
#include <vector>
#include <stack>
#include <unordered_set>
#include <algorithm>
using namespace std;

/** Recursive DFS */
void dfsRecursive(const vector<vector<int>>& graph, int node,
                  unordered_set<int>& visited) {
    visited.insert(node);
    cout << node << " ";

    for (int neighbor : graph[node]) {
        if (visited.find(neighbor) == visited.end()) {
            dfsRecursive(graph, neighbor, visited);
        }
    }
}

/** Iterative DFS using an explicit stack */
vector<int> dfsIterative(const vector<vector<int>>& graph, int start) {
    unordered_set<int> visited;
    stack<int> stk;
    vector<int> order;

    stk.push(start);

    while (!stk.empty()) {
        int node = stk.top();       // Pop from top
        stk.pop();

        if (visited.count(node)) continue;

        visited.insert(node);
        order.push_back(node);

        // Push neighbors in reverse order so first neighbor is on top
        for (int i = graph[node].size() - 1; i >= 0; i--) {
            if (!visited.count(graph[node][i])) {
                stk.push(graph[node][i]);
            }
        }
    }

    return order;
}

int main() {
    vector<vector<int>> graph = {
        {1, 2},    // 0
        {0, 3, 4}, // 1
        {0, 5, 6}, // 2
        {1},       // 3
        {1},       // 4
        {2},       // 5
        {2}        // 6
    };

    vector<int> result = dfsIterative(graph, 0);
    for (int node : result) {
        cout << node << " ";
    }
    // Output: 0 1 3 4 2 5 6
    cout << endl;
    return 0;
}

Complexity Analysis

Like BFS, DFS visits every vertex and edge exactly once. The main difference is the order of exploration, not the asymptotic cost. However, the recursive version may use O(V) stack space in the worst case (e.g., a linear chain graph), which can cause a stack overflow for very deep graphs.

Metric Complexity Description
Time O(V + E) Each vertex is visited once; each edge is examined once
Space O(V) The stack (explicit or call stack) holds at most V entries

DFS vs BFS: Both run in O(V+E) time, but DFS uses less memory on wide graphs (many neighbors per node) while BFS uses less memory on deep, narrow graphs. DFS does not guarantee shortest paths, but it is essential for topological sorting, cycle detection, finding bridges/articulation points, and solving maze/backtracking problems.

Quiz

Test your understanding of DFS with these questions.

Q1. Which data structure does DFS use to determine the next node to visit?

Queue (FIFO)
Stack (LIFO)
Priority Queue
Linked List
DFS uses a stack (Last-In-First-Out), either explicitly or implicitly via the function call stack during recursion. This ensures the most recently discovered node is explored first, giving the "depth-first" behavior. A queue would yield BFS behavior instead.

Q2. Which of the following is NOT a common application of DFS?

Cycle detection
Topological sorting
Finding shortest paths in unweighted graphs
Finding connected components
Finding shortest paths in unweighted graphs is a BFS application, not DFS. DFS does not guarantee shortest paths because it may take a longer path to a node before discovering a shorter one. DFS is used for cycle detection, topological sorting, finding connected/strongly connected components, and backtracking problems.

Q3. Given the graph {0:[1,2], 1:[0,3,4], 2:[0,5,6], 3:[1], 4:[1], 5:[2], 6:[2]}, what is a valid DFS traversal order starting from node 0 (visiting neighbors in listed order)?

0, 1, 3, 4, 2, 5, 6
0, 1, 2, 3, 4, 5, 6
0, 2, 1, 3, 4, 5, 6
0, 3, 1, 4, 2, 6, 5
Starting at 0, DFS visits neighbor 1 first, then goes deep: from 1 visits 3 (a leaf, backtracks), then 4 (a leaf, backtracks to 0), then 2, then 5 (leaf), then 6 (leaf). This gives: 0, 1, 3, 4, 2, 5, 6. Option (b) is the BFS order, not DFS.

Q4. What is the worst-case space complexity of recursive DFS on a graph with V vertices?

O(1)
O(log V)
O(E)
O(V)
In the worst case (e.g., a graph shaped like a long chain: 0-1-2-...-V), the recursive DFS call stack grows to depth V, consuming O(V) space. This is why iterative DFS with an explicit stack is sometimes preferred for very large graphs, as it avoids the system stack size limit.

Q5. How does DFS differ from BFS in terms of traversal order?

DFS visits nodes level by level, BFS goes deep first
DFS explores as deep as possible before backtracking, BFS visits all neighbors at the current depth first
DFS always finds the shortest path, BFS does not
DFS has better time complexity than BFS
DFS explores one branch as deeply as possible before backtracking to try the next branch, using a stack (LIFO). BFS visits all neighbors of the current node before moving to the next depth level, using a queue (FIFO). Both have the same O(V+E) time complexity, but BFS (not DFS) guarantees shortest paths in unweighted graphs.