Browse curriculum

Introduction to DFS

Explore data structures deeply by following a path to its end before backtracking.

What is Depth-First Search?

Depth-First Search (DFS) is a fundamental graph and tree traversal algorithm.

Imagine you are in a maze. A DFS strategy is to pick a path and keep walking until you hit a dead end. Once you hit a wall, you backtrack (retracing your steps) to the last intersection and try a different path. You explore depth first, diving as deep as possible before checking neighbors.

The Motivation: Reaching the Furthest Leaf

Consider a problem where you need to reach a specific node that is known to be very deep in a massive tree, or you need to generate a path from the root to a leaf.

"Given a binary tree, find a path that sums to X."

If the tree has height 1000, the target might be 1000 steps away.

Attempt 1: Breadth-First Search (Why it might fail)

Your instinct might be to check the closest nodes first (Level 1, then Level 2...). This is BFS.

While BFS finds the shortest path, it has a major drawback: Memory Usage.
To check Level 10, you must store ALL nodes of Level 9 in memory. For a full binary tree, the number of nodes doubles at each level. At depth 20, you might need to store 1 million nodes in your Queue just to take one more step! This can lead to Memory Limit Exceeded.

Visualizing the Memory Issue

Picture the tree widening as it goes down. BFS has to hold an entire level in its queue before it can move on, and a level of a full binary tree is as wide as everything above it put together. DFS holds only the path from the root to where it is — one node per level — so its memory grows with the height of the tree, not its width.

When should I use DFS?

Pattern Recognition Triggers:

Use DFS immediately if you see:

  1. "Find ANY path": You don't need the shortest path, just a valid one.
  2. "Visit Every Node": DFS is often cleaner to write recursively than BFS.
  3. Cycle Detection: DFS handles "visited in current path" logic naturally.
  4. Backtracking: Problems that require making choices and undoing them (Sudoku, N-Queens).

The Intuition: Stack & Recursion

DFS logic utilizes a LIFO (Last-In, First-Out) structure.

  1. Visit a node.
  2. Push its children onto a stack (or call recursively).
  3. Pop the most recently added child and visit it.

This means we always process the newest discovery first, leading us deeper and deeper until we run out of children (base case), triggering a backtrack.

Interactive Walkthrough

NODE · LEFT · RIGHT

CALL dfs(root)

Stack depth

0

Visited

0

call stack

No calls in progress

result

Nothing appended yet

 

Call dfs on the root, 1

Next

dfs(1): the node is not empty

Tree

Legend & Complexity

The node whose call is running

A call in progress, waiting for its children

Visited: already appended

Time

O(n)

Space

O(h) call stack

Run preorder DFS on a tree. The call running is filled, the calls waiting underneath it are outlined, and each node shows the order it was appended in. Every call on an empty child is drawn too, as a faded ∅ — that call really happens, and it is where each branch turns back.

Dry Run Table

Tree: [1, 2, 3, 4] — 1 has children 2 and 3, and 2 has a left child 4.

Step Call running Call stack Action result
1 dfs(1) [1] Append 1, go left [1]
2 dfs(2) [1, 2] Append 2, go left [1, 2]
3 dfs(4) [1, 2, 4] Append 4; both children are empty, return [1, 2, 4]
4 dfs(2) [1, 2] Right child is empty: return [1, 2, 4]
5 dfs(1) [1] Left side done, go right [1, 2, 4]
6 dfs(3) [1, 3] Append 3; both children are empty, return [1, 2, 4, 3]
7 - [] dfs(1) finishes: return [1, 2, 4, 3] [1, 2, 4, 3]

The Solution Template

LeetCode 144, Binary Tree Preorder Traversal, in every language: return at an empty node, append the node, then recurse left and then right.

Introduction to DFS Code
1var preorderTraversal = function(root) {
2 const result = [];
3
4 function dfs(node) {
5 if (!node) return;
6 result.push(node.val);
7 dfs(node.left);
8 dfs(node.right);
9 }
10
11 dfs(root);
12 return result;
13};

Edge Cases & Common Mistakes

Edge Case Checklist:

  • Null Node: always check if (!node) return first.
  • Cycles: In graphs, you MUST track visited nodes or you will loop infinitely. (Trees don't have cycles).
  • Stack Overflow: Extremely deep trees can crash recursion. Use iterative stack for production systems dealing with deep data.

Common Mistakes:

  • Forgetting Base Case: Infinite recursion.
  • Wrong Traversal Order: Processing node after children (Post-order) vs before (Pre-order) matters for logic.

Summary

Recap:

  • The Trick: Use the system stack (recursion) to go deep.
  • Movement: Visit -> Recurse Left -> Recurse Right.
  • Efficiency: Low memory usage for balanced trees compared to BFS.

The approach, step by step

  1. Stop at an empty node

    If the node is empty, return immediately.

  2. Process the node

    For preorder, append the node’s value before visiting its children.

  3. Recurse left, then right

    Call DFS on the left child, then on the right child.

Frequently asked questions

What is depth-first search?

A way to visit every node by following one branch all the way down before backing up to try the next. On a binary tree that means finishing a node’s whole left subtree before starting its right subtree.


How does recursion implement DFS?

Each call handles one node and calls itself on the children. The calls still in progress sit on the call stack, so returning from a call is exactly the step back up to the parent.


When should I use DFS instead of BFS?

When you need to explore whole paths, combine answers from children, or keep memory low on wide trees. BFS is the better fit when you need the shortest path or level-by-level order.

Want to see the mechanism first? Step through it in the visualizer:

Depth First Search

Applying for software engineering roles? Free, no sign-up:

AI resume checker — match your resume to the job description