Chapters

Hide chapters

Data Structures & Algorithms in Swift

Third Edition · iOS 13 · Swift 5.1 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

38. Breadth-First Search
Written by Vincent Ngo

In the previous chapter, you explored how graphs can be used to capture relationships between objects. Remember that objects are just vertices, and the relationships between them are represented by edges.

Several algorithms exist to traverse or search through a graph’s vertices. One such algorithm is the breadth-first search (BFS) algorithm.

BFS can be used to solve a wide variety of problems:

  1. Generating a minimum-spanning tree.
  2. Finding potential paths between vertices.
  3. Finding the shortest path between two vertices.

Example

BFS starts off by selecting any vertex in a graph. The algorithm then explores all neighbors of this vertex before traversing the neighbors of said neighbors and so forth. As the name suggests, this algorithm takes a breadth-first approach.

Going through a BFS example using the following undirected graph:

Note: Highlighted vertices represent vertices that have been visited.

You will use a queue to keep track of which vertices to visit next. The first-in-first-out approach of the queue guarantees that all of a vertex’s neighbors are visited before you traverse one level deeper.

  1. To begin, you pick a source vertex to start from. Here, you have chosen A, which is added to the queue.
  2. As long as the queue is not empty, you dequeue and visit the next vertex, in this case A. Next, you add all of A’s neighboring vertices [B, D, C] to the queue.

Note: It’s important to note that you only add a vertex to the queue when it has not yet been visited and is not already in the queue.

  1. The queue is not empty, so you dequeue and visit the next vertex, which is B. You then add B’s neighbor E to the queue. A is already visited so it does not get added. The queue now has [D, C, E].
  2. The next vertex to be dequeued is D. D does not have any neighbors that aren’t visited. The queue now has [C, E].

  1. Next, you dequeue C and add its neighbors [F, G] to the queue. The queue now has [E, F, G].

Note that you have now visited all of A’s neighbors! BFS now moves on to the second level of neighbors.

  1. You dequeue E and add H to the queue. The queue now has [F, G, H]. Note that you don’t add B or F to the queue because B is already visited and F is already in the queue.

  1. You dequeue F, and since all its neighbors are already in the queue or visited, you don’t add anything to the queue.
  2. Just like the previous step, you dequeue G and don’t add anything to the queue.

  1. Finally, you dequeue H. The breadth-first search is complete since the queue is now empty!

  2. When exploring the vertices, you can construct a tree-like structure, showing the vertices at each level: first the vertex you started from, then its neighbors, then its neighbors’ neighbors and so on.

Implementation

Open up the starter playground for this chapter. This playground contains an implementation of a graph that was built in the previous chapter. It also includes a stack-based queue implementation, which you will use to implement BFS.

In your main playground file, you will notice a pre-built sample graph. Add the following below:

extension Graph where Element: Hashable {

  func breadthFirstSearch(from source: Vertex<Element>)
      -> [Vertex<Element>] {
    var queue = QueueStack<Vertex<Element>>()
    var enqueued: Set<Vertex<Element>> = []
    var visited: [Vertex<Element>] = []

    // more to come

    return visited
  }
}

Here, you’ve defined a method breadthFirstSearch(from:) that takes in a starting vertex. It uses three data structures:

  1. queue keeps track of the neighboring vertices to visit next.
  2. enqueued remembers which vertices have been enqueued before so you don’t enqueue the same vertex twice. You use a Set type here so that lookup is cheap and only takes O(1).
  3. visited is an array that stores the order in which the vertices were explored.

Next, complete the method by replacing the comment with:

queue.enqueue(source) // 1
enqueued.insert(source)

while let vertex = queue.dequeue() { // 2
  visited.append(vertex) // 3
  let neighborEdges = edges(from: vertex) // 4
  neighborEdges.forEach { edge in
    if !enqueued.contains(edge.destination) { // 5
      queue.enqueue(edge.destination)
      enqueued.insert(edge.destination)
    }
  }
}

Here’s what’s going on:

  1. You initiate the BFS algorithm by first enqueuing the source vertex.
  2. You continue to dequeue a vertex from the queue until the queue is empty.
  3. Every time you dequeue a vertex from the queue, you add it to the list of visited vertices.
  4. Then, you find all edges that start from the current vertex and iterate over them.
  5. For each edge, you check to see if its destination vertex has been enqueued before, and, if not, you add it to the code.

That’s all there is to implementing BFS! Let’s give this algorithm a spin. Add the following code:

let vertices = graph.breadthFirstSearch(from: a)
vertices.forEach { vertex in
  print(vertex)
}

Take note of the order of the explored vertices using BFS:

0: A
1: B
2: C
3: D
4: E
5: F
6: G
7: H

One thing to keep in mind with neighboring vertices is that the order in which you visit them is determined by how you construct your graph. You could have added an edge between A and C before adding one between A and B. In this case, the output would list C before B.

Performance

When traversing a graph using BFS, each vertex is enqueued once. This has a time complexity of O(V). During this traversal, you also visit all the the edges. The time it takes to visit all edges is O(E). This means that the overall time complexity for breadth-first search is O(V + E).

The space complexity of BFS is O(V), since you have to store the vertices in three separate structures: queue, enqueued and visited.

Key points

  • Breadth-first search (BFS) is an algorithm for traversing or searching a graph.
  • BFS explores all the current vertex’s neighbors before traversing the next level of vertices.
  • It’s generally good to use this algorithm when your graph structure has a lot of neighboring vertices or when you need to find out every possible outcome.
  • The queue data structure is used to prioritize traversing a vertex’s neighboring edges before diving down a level deeper.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.