JavaScript · Course navigation

JavaScript graphs: BFS, DFS, and path existence

Store a graph as a Map of neighbour lists and search it breadth-first and depth-first.

On this page
Before you start

JavaScript Map, Set, and recursion.

  • Store a graph as a Map of neighbour lists and search it breadth-first and depth-first.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

A graph is a set of nodes joined by edges. An adjacency list stores the neighbours of each node, here in a Map from a node name to an array. Breadth-first search (BFS) explores layer by layer with a queue and finds the fewest-edges path. Depth-first search (DFS) follows one path as far as it can before backing up, with recursion or an explicit stack.

Both searches keep a Set of visited nodes so that cycles do not cause endless loops. Checking whether a path exists between two nodes is a search from one to see if it reaches the other. The order in which neighbours are listed decides the visiting order, so the two traversals below visit the same nodes in different orders.

Code example

JavaScript
const graph = new Map([
  ["A", ["B", "C"]],
  ["B", ["D"]],
  ["C", ["D", "E"]],
  ["D", ["F"]],
  ["E", ["F"]],
  ["F", []],
  ["X", ["Y"]],
  ["Y", []],
]);

function bfs(start) {
  const order = [];
  const seen = new Set([start]);
  const queue = [start];
  while (queue.length > 0) {
    const node = queue.shift();
    order.push(node);
    for (const next of graph.get(node)) {
      if (!seen.has(next)) {
        seen.add(next);
        queue.push(next);
      }
    }
  }
  return order;
}

function dfs(node, seen = new Set(), order = []) {
  seen.add(node);
  order.push(node);
  for (const next of graph.get(node)) {
    if (!seen.has(next)) {
      dfs(next, seen, order);
    }
  }
  return order;
}

console.log("bfs: " + bfs("A").join(" "));
console.log("dfs: " + dfs("A").join(" "));
console.log("A reaches F: " + dfs("A").includes("F"));
console.log("A reaches Y: " + dfs("A").includes("Y"));
Output
bfs: A B C D E F
dfs: A B D F C E
A reaches F: true
A reaches Y: false

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1const graph = new Map([
2 ["A", ["B", "C"]],
3 ["B", ["D"]],
4 ["C", ["D", "E"]],
5 ["D", ["F"]],
6 ["E", ["F"]],
7 ["F", []],
8 ["X", ["Y"]],
9 ["Y", []],
10]);
11
12function bfs(start) {
13 const order = [];
14 const seen = new Set([start]);
15 const queue = [start];
16 while (queue.length > 0) {
17 const node = queue.shift();
18 order.push(node);
19 for (const next of graph.get(node)) {
20 if (!seen.has(next)) {
21 seen.add(next);
22 queue.push(next);
23 }
24 }
25 }
26 return order;
27}
28
29function dfs(node, seen = new Set(), order = []) {
30 seen.add(node);
31 order.push(node);
32 for (const next of graph.get(node)) {
33 if (!seen.has(next)) {
34 dfs(next, seen, order);
35 }
36 }
37 return order;
38}
39
40console.log("bfs: " + bfs("A").join(" "));
41console.log("dfs: " + dfs("A").join(" "));
42console.log("A reaches F: " + dfs("A").includes("F"));
43console.log("A reaches Y: " + dfs("A").includes("Y"));

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Each node maps to the list of nodes it points to. X and Y form a separate piece.—
—
BFS starts with the start node in a queue.queue = [A]
—
A node is marked when it is queued, so it never enters the queue twice.queue = [A]
—
BFS visits A, then its neighbours B and C, then the next layer D and E, then F.queue = [A]
bfs: A B C D E F
DFS goes A, B, D, F to the bottom, backs up, and then visits C and E.queue = [A]
bfs: A B C D E F
dfs: A B D F C E
F is reachable.queue = [A]
bfs: A B C D E F
dfs: A B D F C E
A reaches F: true
Y is in a different component, so it is not reachable from A.queue = [A]
bfs: A B C D E F
dfs: A B D F C E
A reaches F: true
A reaches Y: false

Behind the scenes

How JavaScript processes this code

A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.

Cost and common mistakes

BFS and DFS are O(V + E).

  • queue.shift() is O(n) on a large array, so BFS on a large graph needs a proper queue (for example with a head index).
  • Without a visited set, a graph with a cycle makes both searches loop forever.
  • Recursive DFS can exceed the call stack on a long chain of nodes. An explicit stack avoids it.

Try it yourself

How would you get the shortest path between two nodes?

Reveal answer and reasoning

Run BFS and record each node's parent when it is first reached, then follow the parents back from the goal to the start.

Further reading