Recommended resources
The freely available resources this lesson will direct you to.
In our previous lesson, we explored the "deep" paths of a binary tree using recursive depth-first traversals. We learned to navigate by going as far down one branch as possible before backtracking. Now, we'll shift our perspective from depth to breadth.
This lesson introduces Breadth-First Search (BFS), a traversal strategy that explores a tree level by level. Instead of diving deep, we'll visit all nodes at the current depth before moving on to the next. This "wide" exploration is essential for a different class of problems, such as finding the shortest path in an unweighted graph (a topic we'll cover later) or any task that requires processing nodes in order of their distance from the root.
The key to managing this level-by-level exploration is a data structure you've encountered before: the queue. Its First-In, First-Out (FIFO) property is perfectly suited to ensure we process nodes in the order we discover them. Your experience as a front-end developer has given you an intuition for ordered processing, like handling user events or managing rendering queues, and we'll apply that same principle here in a more structured, algorithmic context.
The Essence of Breadth-First Search
Imagine a tree's nodes are balls connected by strings. If you grab the root node and pull it upwards, the nodes will naturally arrange themselves into layers based on their distance from the root. BFS is the algorithmic equivalent of visiting the nodes in the first layer, then the second, then the third, and so on.
The following video from Reducible provides an excellent analogy to build this intuition. It discusses BFS in the context of general graphs, but the core idea of visiting neighbors level by level applies directly to trees.
Breadth First Search (BFS) Visualized and Explained
Watch this segment to understand the core concept of BFS and how it naturally discovers nodes in layers based on their distance from a starting point.
Focus on the "balls with strings" analogy from the beginning and the explanation of visiting nodes at distance 1, then distance 2, etc., from this section.
As the video explains, the key to implementing this is a queue. Here’s how the process works:
- Initialize: Create a queue and add the root node to it.
- Loop: While the queue is not empty:
a. Dequeue: Remove the node from the front of the queue.
b. Visit: Process the node (e.g., print its value).
c. Enqueue Children: Add the node's left child (if it exists) to the back of the queue. Then, add its right child (if it exists) to the back of the queue.
This FIFO process guarantees that nodes added at a certain level are processed completely before the nodes they, in turn, add (which belong to the next level).
This animated GIF provides a perfect visual of the algorithm in action. Watch how nodes are added to the "Frontier Queue" and processed in the order they entered, ensuring a level-by-level traversal.

Grouping Nodes by Level
The basic algorithm described above will visit all nodes in the correct level order, but it will produce a flat list of values (e.g., [A, B, C, D, E, F, G]). In many interview problems, you are required to return the values grouped by level, like [[A], [B, C], [D, E, F, G]].
To achieve this, we need a small but crucial modification to the algorithm. This is a very common interview pattern. The trick is to know, at the start of each level, exactly how many nodes are on that level.
How can we know that? The number of nodes on the current level is simply the current size of the queue.
Here is the refined algorithm:
- Initialize: Create a queue, add the root node, and create a main
resultlist. - Outer Loop: While the queue is not empty:
a. Get Level Size: Store the current size of the queue in a variable,levelSize.
b. Initialize Level List: Create a new list for the current level's values,currentLevel.
c. Inner Loop: LooplevelSizetimes:
i. Dequeue: Remove a node from the front of the queue.
ii. Add to Level: Add the node's value to thecurrentLevellist.
iii. Enqueue Children: Add the node's left and right children to the back of the queue.
d. Add to Result: After the inner loop finishes, add thecurrentLevellist to the mainresultlist.
This structure ensures that the inner loop processes exactly one level's worth of nodes before the outer loop moves on to the next.
The following video from Andy Gala provides a fantastic, step-by-step walkthrough of this specific technique in JavaScript. It clearly explains the common pitfalls of a naive approach and the elegance of using the queue's length to manage levels.
LEETCODE 102 (JAVASCRIPT) | BINARY TREE LEVEL ORDER TRAVERSAL
Watch this detailed explanation of the level-grouping technique. The presenter does an excellent job visualizing how the queue and result array evolve.
Pay close attention to the visual walkthrough from the beginning, where the presenter uses the len variable to control the processing of each level. He also explains why this is necessary in the segment from this point.
Implementation in TypeScript
Now, let's translate this logic into a TypeScript function. We'll use our TreeNode class from the previous lessons. For the queue, a standard JavaScript array works well; we'll use push to enqueue and shift to dequeue.
class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val: number, left: TreeNode | null = null, right: TreeNode | null = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function levelOrder(root: TreeNode | null): number[][] {
// If the tree is empty, return an empty array.
if (!root) {
return [];
}
const result: number[][] = [];
const queue: TreeNode[] = [root];
// Continue as long as there are nodes to process.
while (queue.length > 0) {
// The number of nodes at the current level.
const levelSize = queue.length;
const currentLevel: number[] = [];
// Process exactly `levelSize` nodes.
for (let i = 0; i < levelSize; i++) {
// Dequeue the first node in the queue.
const node = queue.shift()!; // Using '!' since queue.length > 0 check ensures it's not empty.
// Add its value to the list for the current level.
currentLevel.push(node.val);
// Enqueue children for the next level.
if (node.left) {
queue.push(node.left);
}
if (node.right) {
queue.push(node.right);
}
}
// Add the completed level to the final result.
result.push(currentLevel);
}
return result;
}
Code Walkthrough
Let's trace this function with the tree from the image resource BFS with Queue.

-
Initial State:
result = []queue = [Node(1)]
-
Level 0 (Outer loop, 1st iteration):
levelSizebecomes1.currentLevel = [].- Inner loop runs once:
node = Node(1)is dequeued.queueis now[].currentLevelbecomes[1].Node(2)andNode(3)are enqueued.queueis now[Node(2), Node(3)].
- The
currentLevel[1]is pushed toresult.resultis now[[1]].
-
Level 1 (Outer loop, 2nd iteration):
levelSizebecomes2.currentLevel = [].- Inner loop runs twice:
- i=0:
node = Node(2)is dequeued.queueis now[Node(3)].currentLevelbecomes[2].Node(4)andNode(5)are enqueued.queueis now[Node(3), Node(4), Node(5)]. - i=1:
node = Node(3)is dequeued.queueis now[Node(4), Node(5)].currentLevelbecomes[2, 3].Node(6)andNode(7)are enqueued.queueis now[Node(4), Node(5), Node(6), Node(7)].
- i=0:
- The
currentLevel[2, 3]is pushed toresult.resultis[[1], [2, 3]].
-
Level 2 (Outer loop, 3rd iteration):
levelSizebecomes4.currentLevel = [].- Inner loop runs four times, dequeuing nodes 4, 5, 6, and 7. They have no children.
currentLevelbecomes[4, 5, 6, 7].- The
currentLevelis pushed toresult.resultis now[[1], [2, 3], [4, 5, 6, 7]].
-
Termination:
- The
queueis now empty. Thewhileloop terminates. - The final
resultis returned.
- The
Complexity and Pitfalls
- Time Complexity: , where is the number of nodes. Each node is enqueued and dequeued exactly once.
- Space Complexity: , where is the maximum width of the tree. The space is determined by the maximum number of nodes in the queue at any one time. In the worst case (a complete, balanced binary tree), the last level can contain roughly nodes, making the space complexity .
This pattern is powerful, but small mistakes can lead to incorrect results. To solidify your understanding, it's worth reviewing common errors. The resource from Algo.monster provides an excellent breakdown of these issues.
102. Binary Tree Level Order Traversal
This section of the article details frequent mistakes made when implementing level-order traversal. Recognizing these will help you write more robust code in an interview setting.
Read the entire Common Pitfalls section. Pay special attention to the first point about tracking level boundaries and the second point about using an inefficient queue structure (pop(0) on a list).
Conclusion
In this lesson, you've added a fundamental traversal algorithm to your toolkit. While depth-first search explores vertically, breadth-first search gives you a way to explore horizontally.
Here are the key takeaways:
- Breadth-First Search (BFS) traverses a tree level by level.
- The queue data structure (FIFO) is the natural choice for implementing BFS.
- To group nodes by level (a common interview requirement), you must capture the queue's size at the start of each level and loop that many times.
So far, we have learned how to visit every node in a tree using both DFS and BFS. In our next lesson, we will build on this foundation to compute properties of a tree, such as its height, by collecting and combining information during a traversal.