94. Binary Tree Inorder Traversal
Problem Description
You are given the root of a binary tree. Your task is to return the values of all nodes in the tree using inorder traversal.
Inorder traversal is a way to visit all nodes in a binary tree following this specific order:
- First, visit all nodes in the left subtree
- Then, visit the root node
- Finally, visit all nodes in the right subtree
This pattern is applied recursively to every node in the tree. For each node encountered, you follow the same left-root-right pattern.
For example, if you have a binary tree like this:
1 \ 2 / 3
The inorder traversal would visit the nodes in this sequence:
- Start at root (1), go to left subtree (empty)
- Visit root (1)
- Go to right subtree (2)
- At node 2, go to left subtree (3)
- At node 3, left is empty, visit 3, right is empty
- Back to node 2, visit 2
- Right subtree of 2 is empty
So the result would be [1, 3, 2]
.
The function should return a list containing all the node values in the order they were visited during the inorder traversal. If the tree is empty (root is None
), return an empty list.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure, which is a hierarchical data structure with a root node and child nodes.
DFS
- Yes: Since we identified this as a tree problem, the flowchart leads us directly to DFS (Depth-First Search).
Conclusion: The flowchart suggests using DFS (Depth-First Search) for traversing the binary tree. This makes perfect sense for inorder traversal because:
- We need to visit nodes in a specific order (left subtree → root → right subtree)
- We need to go deep into the left subtree first before processing the current node
- The recursive nature of DFS naturally handles the nested structure of the tree
- DFS allows us to maintain the call stack to remember which nodes to visit after completing subtree traversals
The solution implements DFS through recursion, where the function calls itself for the left subtree, processes the current node, then calls itself for the right subtree - perfectly matching the inorder traversal pattern.
Intuition
When we think about traversing a binary tree in a specific order, we need to consider how to systematically visit every node exactly once. The inorder traversal follows a "left-root-right" pattern, which means for any given node, we must:
- First completely process everything in its left subtree
- Then process the node itself
- Finally process everything in its right subtree
This naturally suggests a recursive approach because the same pattern applies at every level of the tree. Each subtree is itself a tree that needs to be traversed in the same manner.
The key insight is that we can use the function call stack to our advantage. When we make a recursive call to traverse the left subtree, the current node's information is automatically saved on the call stack. After the left subtree is fully processed and that recursive call returns, we're back at the current node and can process it. Then we make another recursive call for the right subtree.
Think of it like reading a book with nested chapters:
- Before reading a chapter (visiting a node), you must read all its sub-chapters on the left
- Then read the current chapter
- Finally read all sub-chapters on the right
The base case is straightforward: when we encounter a None
node (empty subtree), we simply return without doing anything. This stops the recursion from going infinitely deep.
By maintaining a shared list ans
that collects node values in the order we visit them, we build up our final result. Each time we "visit" a node (after traversing its left subtree), we append its value to this list. The recursion ensures nodes are visited in the correct inorder sequence.
Learn more about Stack, Tree, Depth-First Search and Binary Tree patterns.
Solution Approach
The solution implements recursive traversal to visit all nodes in the binary tree following the inorder pattern (left-root-right).
Implementation Details:
The solution uses a helper function dfs
nested inside the main function. This design allows the helper function to access the ans
list without passing it as a parameter in every recursive call.
Algorithm Steps:
-
Initialize the result list: Create an empty list
ans
to store the node values in traversal order. -
Define the recursive DFS function:
def dfs(root): if root is None: return dfs(root.left) ans.append(root.val) dfs(root.right)
-
Base Case: When
root is None
, the function returns immediately. This handles empty trees and leaf nodes' null children. -
Recursive Cases:
dfs(root.left)
: First, recursively traverse the entire left subtree. This ensures all nodes in the left subtree are processed before the current node.ans.append(root.val)
: After the left subtree is complete, visit the current node by adding its value to the result list.dfs(root.right)
: Finally, recursively traverse the entire right subtree.
-
Start the traversal: Call
dfs(root)
with the tree's root node to begin the traversal. -
Return the result: Return the
ans
list containing all node values in inorder sequence.
Why This Works:
The recursion naturally maintains the correct order through the call stack. When we call dfs(root.left)
, the current function pauses, and all nodes in the left subtree get processed completely before returning to append the current node's value. This ensures the left-root-right order is preserved at every level of the tree.
Time Complexity: O(n)
where n
is the number of nodes, as we visit each node exactly once.
Space Complexity: O(h)
where h
is the height of the tree, due to the recursion call stack. In the worst case (skewed tree), this could be O(n)
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example to see how the recursive inorder traversal works step by step.
Consider this binary tree:
4 / \ 2 6 / \ 1 3
We want to get the inorder traversal: [1, 2, 3, 4, 6]
Step-by-step execution:
-
Start: Call
dfs(4)
with root node 4- Before visiting 4, we must traverse its left subtree
- Call
dfs(2)
-
At node 2:
- Before visiting 2, traverse its left subtree
- Call
dfs(1)
-
At node 1:
- Call
dfs(None)
for left child → returns immediately - Visit node 1:
ans = [1]
- Call
dfs(None)
for right child → returns immediately - Return to node 2
- Call
-
Back at node 2:
- Left subtree complete
- Visit node 2:
ans = [1, 2]
- Call
dfs(3)
for right subtree
-
At node 3:
- Call
dfs(None)
for left child → returns immediately - Visit node 3:
ans = [1, 2, 3]
- Call
dfs(None)
for right child → returns immediately - Return to node 2, then to node 4
- Call
-
Back at node 4:
- Left subtree complete
- Visit node 4:
ans = [1, 2, 3, 4]
- Call
dfs(6)
for right subtree
-
At node 6:
- Call
dfs(None)
for left child → returns immediately - Visit node 6:
ans = [1, 2, 3, 4, 6]
- Call
dfs(None)
for right child → returns immediately - Return to node 4
- Call
-
Complete: Return
ans = [1, 2, 3, 4, 6]
The key insight is how the recursion stack maintains the correct order. Each dfs(root.left)
call must fully complete before we append the current node's value, ensuring all left descendants are processed first. The same pattern applies at every level, naturally producing the inorder sequence.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import Optional, List
9
10class Solution:
11 def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
12 """
13 Performs inorder traversal of a binary tree.
14
15 Args:
16 root: The root node of the binary tree.
17
18 Returns:
19 A list containing the values of nodes in inorder sequence.
20 """
21
22 def traverse_inorder(node: Optional[TreeNode]) -> None:
23 """
24 Helper function to recursively traverse the tree in inorder.
25 Left subtree -> Current node -> Right subtree
26
27 Args:
28 node: The current node being processed.
29 """
30 # Base case: if node is None, return
31 if node is None:
32 return
33
34 # Traverse left subtree first
35 traverse_inorder(node.left)
36
37 # Process current node (add its value to result)
38 result.append(node.val)
39
40 # Traverse right subtree
41 traverse_inorder(node.right)
42
43 # Initialize result list to store traversal values
44 result: List[int] = []
45
46 # Start traversal from root
47 traverse_inorder(root)
48
49 return result
50
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 // List to store the inorder traversal result
18 private List<Integer> result = new ArrayList<>();
19
20 /**
21 * Performs inorder traversal of a binary tree
22 * @param root The root node of the binary tree
23 * @return List containing values in inorder sequence (left -> root -> right)
24 */
25 public List<Integer> inorderTraversal(TreeNode root) {
26 performInorderDFS(root);
27 return result;
28 }
29
30 /**
31 * Helper method to recursively traverse the tree in inorder
32 * @param node Current node being processed
33 */
34 private void performInorderDFS(TreeNode node) {
35 // Base case: if node is null, return
36 if (node == null) {
37 return;
38 }
39
40 // Traverse left subtree first
41 performInorderDFS(node.left);
42
43 // Process current node (add value to result list)
44 result.add(node.val);
45
46 // Traverse right subtree last
47 performInorderDFS(node.right);
48 }
49}
50
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 /**
15 * Performs inorder traversal of a binary tree
16 * @param root - The root node of the binary tree
17 * @return A vector containing the values of nodes in inorder sequence
18 */
19 vector<int> inorderTraversal(TreeNode* root) {
20 // Vector to store the result of inorder traversal
21 vector<int> result;
22
23 // Lambda function for recursive depth-first search
24 // Captures result vector by reference to modify it
25 function<void(TreeNode*)> dfs = [&](TreeNode* node) {
26 // Base case: if node is null, return
27 if (!node) {
28 return;
29 }
30
31 // Inorder traversal: Left -> Root -> Right
32 dfs(node->left); // Traverse left subtree
33 result.push_back(node->val); // Process current node
34 dfs(node->right); // Traverse right subtree
35 };
36
37 // Start the traversal from root
38 dfs(root);
39
40 return result;
41 }
42};
43
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Performs inorder traversal of a binary tree
17 * @param root - The root node of the binary tree
18 * @returns An array containing the values of nodes in inorder sequence
19 */
20function inorderTraversal(root: TreeNode | null): number[] {
21 // Array to store the traversal result
22 const result: number[] = [];
23
24 /**
25 * Helper function to perform depth-first search in inorder fashion
26 * @param node - Current node being processed
27 */
28 const performInorderDFS = (node: TreeNode | null): void => {
29 // Base case: if node is null, return
30 if (!node) {
31 return;
32 }
33
34 // Traverse left subtree first
35 performInorderDFS(node.left);
36
37 // Process current node by adding its value to result
38 result.push(node.val);
39
40 // Traverse right subtree last
41 performInorderDFS(node.right);
42 };
43
44 // Start the traversal from root
45 performInorderDFS(root);
46
47 return result;
48}
49
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs an inorder traversal of the binary tree using depth-first search (DFS). Each node in the tree is visited exactly once. During each visit, the algorithm performs constant-time operations: checking if the node is None
, appending the node's value to the result list, and making recursive calls. Since we visit all n
nodes exactly once and perform O(1)
operations at each node, the total time complexity is O(n)
.
Space Complexity: O(n)
The space complexity consists of two components:
- Recursive call stack: In the worst case (a skewed tree), the recursion depth can be
O(n)
, requiringO(n)
stack space. In the best case (a balanced tree), the recursion depth would beO(log n)
. - Output array: The
ans
list stores alln
node values, requiringO(n)
space.
Since the output array always requires O(n)
space regardless of tree structure, and the call stack can also require up to O(n)
space in the worst case, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Result List in Multiple Recursive Paths
A common mistake is trying to return and concatenate lists at each recursive call instead of using a shared result list:
Incorrect Approach:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root is None:
return []
# This creates many intermediate lists and concatenations
left_result = self.inorderTraversal(root.left)
current = [root.val]
right_result = self.inorderTraversal(root.right)
return left_result + current + right_result
Issues with this approach:
- Creates many intermediate lists, increasing space complexity
- List concatenation operations add overhead
- Less efficient than appending to a single list
Solution: Use a shared result list with a helper function (as shown in the correct implementation) or pass the list as a parameter.
2. Forgetting the Base Case
Incorrect Code:
def traverse_inorder(node):
# Missing base case check!
traverse_inorder(node.left) # This will fail when node is None
result.append(node.val)
traverse_inorder(node.right)
Issue: This causes an AttributeError when trying to access node.left
on a None object.
Solution: Always check if the node is None before accessing its properties:
def traverse_inorder(node):
if node is None:
return
# Rest of the code...
3. Using Global Variables Instead of Encapsulation
Problematic Pattern:
result = [] # Global variable
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
global result
result = [] # Need to reset for each test case
self.traverse(root)
return result
Issues:
- Global state persists between function calls
- Can cause incorrect results when the function is called multiple times
- Makes the code harder to test and debug
Solution: Keep the result list local to the function scope using nested functions or instance variables.
4. Incorrect Traversal Order
Common Mistake:
def traverse_inorder(node):
if node is None:
return
result.append(node.val) # Processing node BEFORE left subtree
traverse_inorder(node.left) # This would be preorder, not inorder!
traverse_inorder(node.right)
Issue: This implements preorder traversal (root-left-right) instead of inorder (left-root-right).
Solution: Ensure the correct order:
- Process left subtree first
- Process current node
- Process right subtree last
5. Not Handling Edge Cases Properly
Incomplete Implementation:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
def traverse(node):
traverse(node.left) # What if root itself is None?
result.append(node.val)
traverse(node.right)
traverse(root)
return result
Issue: Doesn't handle the case where the root itself is None (empty tree).
Solution: Either check root before calling the helper function or handle None in the helper function itself:
def traverse(node):
if node is None: # Handles both empty tree and leaf nodes
return
# Rest of the traversal logic
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Want a Structured Path to Master System Design Too? Don’t Miss This!