Symmetric Tree
Description
Given the root of a binary tree, determine whether the tree is symmetric — that is, whether it is a mirror image of itself around its center.
A binary tree is symmetric if you could draw a vertical line through the root and the left half would be a perfect reflection of the right half. Specifically, for every node on the left side, there must be a corresponding node on the right side at the mirrored position with the same value.

Examples
Example 1
Input: root = [1, 2, 2, 3, 4, 4, 3]
1
/ \
2 2
/ \ / \
3 4 4 3
Output: true
Explanation: The left subtree has structure [2, 3, 4] and the right subtree has structure [2, 4, 3]. When you mirror the right subtree (swap all left and right children), it becomes [2, 3, 4] — identical to the left subtree. Every node on the left has a matching node at the mirrored position on the right with the same value.
Example 2
Input: root = [1, 2, 2, null, 3, null, 3]
1
/ \
2 2
\ \
3 3
Output: false
Explanation: The left subtree's right child is 3, and the right subtree's right child is also 3. But for symmetry, the left subtree's right child should match the right subtree's LEFT child (which is null). The 3 on the right side is in the wrong mirrored position, so the tree is not symmetric.
Example 3
Input: root = [1]
1
Output: true
Explanation: A single node with no children is trivially symmetric — there is nothing to mismatch.
Constraints
- The number of nodes in the tree is in the range [1, 1000]
- -100 ≤ Node.val ≤ 100
Editorial
Brute Force
Intuition
The most natural way to check symmetry is to think about what "mirror image" actually means. If you fold the tree along the root's vertical axis, the left subtree should land exactly on the right subtree.
More concretely, two subtrees are mirrors of each other if:
- Their root values are the same.
- The left child of the left subtree mirrors the right child of the right subtree.
- The right child of the left subtree mirrors the left child of the right subtree.
This is naturally recursive. Think of two people standing on opposite sides of a mirror — whatever the left person does with their left hand, the right person does with their right hand, and vice versa. We need to check every pair of "reflected" nodes.
The brute force approach uses a simple recursive helper function isMirror(left, right) that checks if two subtrees are mirror images.
Step-by-Step Explanation
Let's trace through Example 1:
1
/ \
2 2
/ \ / \
3 4 4 3
Step 1: Start at root 1. Call isMirror(root.left=2, root.right=2).
Step 2: Compare the two nodes: left=2, right=2. Values match (2 == 2). Now check their children in mirrored order.
Step 3: Recursive call 1: isMirror(left.left=3, right.right=3). The outer children must match.
Step 4: Compare: left=3, right=3. Values match (3 == 3). Both have no children → both calls return true.
Step 5: Recursive call 2: isMirror(left.right=4, right.left=4). The inner children must match.
Step 6: Compare: left=4, right=4. Values match (4 == 4). Both have no children → both calls return true.
Step 7: Both recursive calls returned true, so isMirror(2, 2) returns true. The tree is symmetric.
Now let's verify Example 2 (asymmetric):
1
/ \
2 2
\ \
3 3
Step 8: Call isMirror(root.left=2, root.right=2). Values match.
Step 9: Recursive call 1: isMirror(left.left=null, right.right=3). Left is null but right is not → MISMATCH. Return false immediately.
Step 10: Since one recursive call returned false, the tree is NOT symmetric. Return false.
Recursive Mirror Check — Symmetric Tree Verification — Watch how the recursive function compares pairs of mirrored nodes, checking outer pairs and inner pairs at each level.
Algorithm
- If root is null, return true (empty tree is symmetric).
- Define a helper function isMirror(left, right):
a. If both left and right are null, return true (matching empty subtrees).
b. If only one is null, return false (structural mismatch).
c. If left.val ≠ right.val, return false (value mismatch).
d. Recursively check: isMirror(left.left, right.right) AND isMirror(left.right, right.left). - Return isMirror(root.left, root.right).
Code
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
bool isMirror(TreeNode* left, TreeNode* right) {
// Both null means matching empty subtrees
if (!left && !right) return true;
// One null and one non-null means structural mismatch
if (!left || !right) return false;
// Values must match, and children must be mirrored
return (left->val == right->val)
&& isMirror(left->left, right->right)
&& isMirror(left->right, right->left);
}
bool isSymmetric(TreeNode* root) {
if (!root) return true;
return isMirror(root->left, root->right);
}
};class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def isMirror(left, right):
# Both None means matching empty subtrees
if not left and not right:
return True
# One None and one non-None means structural mismatch
if not left or not right:
return False
# Values must match, and children must be mirrored
return (left.val == right.val
and isMirror(left.left, right.right)
and isMirror(left.right, right.left))
if not root:
return True
return isMirror(root.left, root.right)class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isMirror(root.left, root.right);
}
private boolean isMirror(TreeNode left, TreeNode right) {
// Both null means matching empty subtrees
if (left == null && right == null) return true;
// One null and one non-null means structural mismatch
if (left == null || right == null) return false;
// Values must match, and children must be mirrored
return (left.val == right.val)
&& isMirror(left.left, right.right)
&& isMirror(left.right, right.left);
}
}Complexity Analysis
Time Complexity: O(n)
We visit each node at most once. At each node, we do O(1) work — compare values and make recursive calls. In the worst case (fully symmetric tree), we check all n nodes. If the tree is asymmetric, we may detect it early and short-circuit, but worst case is still O(n).
Space Complexity: O(h)
The recursion depth is the height of the tree. For a balanced tree this is O(log n), and for a completely skewed tree this is O(n). Since the problem constrains up to 1000 nodes, the maximum recursion depth is 1000, which is within typical stack limits but worth noting.
Why This Approach Is Not Efficient
The recursive approach is actually O(n) time and works well for this problem. However, it has a practical limitation: it uses the call stack, which has a fixed size. For very deep trees (height approaching n), the recursion can cause a stack overflow.
With up to 1000 nodes, a completely skewed tree would have height 1000, creating 1000 recursive calls. While most systems handle this, it is fragile — and the follow-up question specifically asks for an iterative solution.
The key insight is that we can replace the implicit recursion stack with an explicit data structure (a stack or queue) that we manage ourselves. This eliminates the risk of stack overflow and gives us more control over memory usage. The iterative approach uses the same mirror-comparison logic but processes node pairs using a stack instead of recursive calls.
Better Approach - Iterative with Stack
Intuition
Instead of using recursion, we simulate the same process with an explicit stack. We push pairs of nodes that should be mirrors of each other. Initially, we push (root.left, root.right). Then, at each step, we pop a pair, compare their values, and push their children in mirrored order:
- Push (left.left, right.right) — outer children must match
- Push (left.right, right.left) — inner children must match
If at any point a pair has mismatched values or one is null while the other isn't, the tree is not symmetric. If we process all pairs without finding a mismatch, the tree is symmetric.
This is exactly the same logic as recursion, but we manage the "to-do list" of pairs ourselves using a stack.
Step-by-Step Explanation
Let's trace through the asymmetric Example 2 to see how the stack detects the mismatch:
1
/ \
2 2
\ \
3 3
Step 1: Initialize stack with pair (root.left=2, root.right=2). Stack: [(2, 2)].
Step 2: Pop pair (2, 2). Both non-null, values match (2 == 2). Push children in mirror order:
- Push (left.left=null, right.right=3) — outer children
- Push (left.right=3, right.left=null) — inner children
- Stack: [(null, 3), (3, null)].
Step 3: Pop pair (3, null). Left is non-null (3) but right is null. Structural mismatch detected! Return false immediately.
The tree is NOT symmetric. We detected the mismatch after checking only 2 pairs instead of visiting all nodes.
Now let's trace the symmetric Example 1:
1
/ \
2 2
/ \ / \
3 4 4 3
Step 4: Stack starts with [(2, 2)].
Step 5: Pop (2, 2). Match. Push: (left.left=3, right.right=3) and (left.right=4, right.left=4). Stack: [(3, 3), (4, 4)].
Step 6: Pop (4, 4). Match. Both are leaves. Push: (null, null) and (null, null). Stack: [(3, 3), (null, null), (null, null)].
Step 7: Pop (null, null). Both null → skip, continue. Stack: [(3, 3), (null, null)].
Step 8: Pop (null, null). Both null → skip. Stack: [(3, 3)].
Step 9: Pop (3, 3). Match. Both are leaves. Push null pairs. All get skipped.
Step 10: Stack is empty. All pairs matched. Return true.
Iterative Stack — Mirror Pair Comparison — Watch how pairs of nodes are pushed and popped from the stack. Each pair represents two nodes that should be mirrors. A mismatch means the tree is asymmetric.
Algorithm
- If root is null, return true.
- Initialize a stack and push the pair (root.left, root.right).
- While the stack is not empty:
a. Pop a pair (node1, node2).
b. If both are null, continue (skip this pair).
c. If one is null and the other isn't, return false.
d. If node1.val ≠ node2.val, return false.
e. Push (node1.left, node2.right) — outer children pair.
f. Push (node1.right, node2.left) — inner children pair. - If the stack empties without mismatches, return true.
Code
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) return true;
stack<pair<TreeNode*, TreeNode*>> st;
st.push({root->left, root->right});
while (!st.empty()) {
auto [left, right] = st.top();
st.pop();
// Both null — this pair is fine
if (!left && !right) continue;
// One null, other not — structural mismatch
if (!left || !right) return false;
// Value mismatch
if (left->val != right->val) return false;
// Push children in mirror order
st.push({left->left, right->right}); // outer pair
st.push({left->right, right->left}); // inner pair
}
return true;
}
};class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
stack = [(root.left, root.right)]
while stack:
left, right = stack.pop()
# Both None — this pair is fine
if not left and not right:
continue
# One None, other not — structural mismatch
if not left or not right:
return False
# Value mismatch
if left.val != right.val:
return False
# Push children in mirror order
stack.append((left.left, right.right)) # outer pair
stack.append((left.right, right.left)) # inner pair
return Trueclass Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
Deque<TreeNode[]> stack = new ArrayDeque<>();
stack.push(new TreeNode[]{root.left, root.right});
while (!stack.isEmpty()) {
TreeNode[] pair = stack.pop();
TreeNode left = pair[0];
TreeNode right = pair[1];
// Both null — this pair is fine
if (left == null && right == null) continue;
// One null, other not — structural mismatch
if (left == null || right == null) return false;
// Value mismatch
if (left.val != right.val) return false;
// Push children in mirror order
stack.push(new TreeNode[]{left.left, right.right}); // outer pair
stack.push(new TreeNode[]{left.right, right.left}); // inner pair
}
return true;
}
}Complexity Analysis
Time Complexity: O(n)
We process each node at most once (as part of a pair). Each pair comparison is O(1). In the worst case we check all n/2 pairs (roughly n nodes), giving O(n) total time. Early termination on asymmetric trees can make it faster in practice.
Space Complexity: O(n)
In the worst case, the stack can hold up to n/2 pairs. For a complete binary tree, the last level has roughly n/2 nodes, so before processing any leaf pairs, the stack could hold O(n) entries. This is technically O(n) space, compared to O(h) for recursion on balanced trees, but it avoids call stack overflow risks.
Why This Approach Is Not Efficient
The iterative stack approach is already O(n) time, which is optimal since we must examine every node. However, it processes pairs in a depth-first order because it uses a stack (LIFO). This means it explores one branch fully before checking the other.
A queue-based approach processes pairs in breadth-first (level-by-level) order. This can be more intuitive for symmetry checking because symmetry is inherently a level-by-level property — at each level, the nodes should be a palindrome. Using a queue also gives a slightly different traversal pattern that mirrors how humans naturally verify symmetry: checking one level at a time from top to bottom.
Both approaches have the same time and space complexity, but the queue-based approach provides a natural level-order perspective.
Optimal Approach - Iterative with Queue
Intuition
Instead of a stack, we use a queue to process mirror pairs in level order — breadth-first. The logic is identical to the stack approach: enqueue pairs that should be mirrors, dequeue and compare, enqueue their children in mirrored order.
The key difference is traversal order. With a queue (FIFO), we check all pairs at one level before moving to the next. This mirrors how our eyes naturally scan for symmetry: we look at the top of a shape first, then scan downward level by level.
For symmetric trees, this processes the same pairs in a slightly different order. For asymmetric trees, it may detect mismatches at shallower levels first (which is often where mismatches occur), potentially checking fewer total pairs.
Step-by-Step Explanation
Let's trace through Example 1 (symmetric tree) using a queue:
1
/ \
2 2
/ \ / \
3 4 4 3
Step 1: Initialize queue with pair (root.left=2, root.right=2). Queue: [(2, 2)].
Step 2: Dequeue pair (2, 2). Both non-null, values match. Enqueue children in mirror order:
- Enqueue (left.left=3, right.right=3) — outer pair
- Enqueue (left.right=4, right.left=4) — inner pair
- Queue: [(3, 3), (4, 4)].
Step 3: Dequeue pair (3, 3). Both non-null, values match (3 == 3). Both are leaves. Enqueue their null children:
- Enqueue (null, null) and (null, null).
- Queue: [(4, 4), (null, null), (null, null)].
Step 4: Dequeue pair (4, 4). Both non-null, values match (4 == 4). Both are leaves. Enqueue null pairs.
- Queue: [(null, null), (null, null), (null, null), (null, null)].
Step 5: Dequeue (null, null). Both null → skip. Continue.
Step 6: Dequeue (null, null). Both null → skip. Continue.
Step 7: Dequeue (null, null). Both null → skip. Continue.
Step 8: Dequeue (null, null). Both null → skip. Queue is empty.
Step 9: All pairs checked without mismatch. Return true.
Queue-Based Level-Order Symmetry Check — Watch how mirror pairs are enqueued and dequeued level by level. The queue ensures we verify symmetry from the top of the tree downward.
Algorithm
- If root is null, return true.
- Initialize a queue and enqueue the pair (root.left, root.right).
- While the queue is not empty:
a. Dequeue a pair (node1, node2).
b. If both are null, continue.
c. If one is null and the other isn't, return false.
d. If node1.val ≠ node2.val, return false.
e. Enqueue (node1.left, node2.right) — outer children pair.
f. Enqueue (node1.right, node2.left) — inner children pair. - If the queue empties without mismatches, return true.
Code
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) return true;
queue<pair<TreeNode*, TreeNode*>> q;
q.push({root->left, root->right});
while (!q.empty()) {
auto [left, right] = q.front();
q.pop();
// Both null — matching empty subtrees
if (!left && !right) continue;
// One null, other not — structural mismatch
if (!left || !right) return false;
// Value mismatch
if (left->val != right->val) return false;
// Enqueue children in mirror order
q.push({left->left, right->right}); // outer pair
q.push({left->right, right->left}); // inner pair
}
return true;
}
};from collections import deque
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
queue = deque([(root.left, root.right)])
while queue:
left, right = queue.popleft()
# Both None — matching empty subtrees
if not left and not right:
continue
# One None, other not — structural mismatch
if not left or not right:
return False
# Value mismatch
if left.val != right.val:
return False
# Enqueue children in mirror order
queue.append((left.left, right.right)) # outer pair
queue.append((left.right, right.left)) # inner pair
return Trueimport java.util.*;
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
Queue<TreeNode[]> queue = new LinkedList<>();
queue.offer(new TreeNode[]{root.left, root.right});
while (!queue.isEmpty()) {
TreeNode[] pair = queue.poll();
TreeNode left = pair[0];
TreeNode right = pair[1];
// Both null — matching empty subtrees
if (left == null && right == null) continue;
// One null, other not — structural mismatch
if (left == null || right == null) return false;
// Value mismatch
if (left.val != right.val) return false;
// Enqueue children in mirror order
queue.offer(new TreeNode[]{left.left, right.right}); // outer pair
queue.offer(new TreeNode[]{left.right, right.left}); // inner pair
}
return true;
}
}Complexity Analysis
Time Complexity: O(n)
Every node is processed exactly once as part of a mirror pair. Each comparison is O(1). Total: O(n) time. Same as the recursive and stack approaches — this is optimal since we must inspect every node to confirm symmetry.
Space Complexity: O(n)
In the worst case (a complete binary tree), the queue can hold up to O(n/2) pairs at the widest level. For a tree with 1000 nodes, this is at most about 500 pairs. The queue-based approach uses O(n) space in the worst case, same as the stack approach. Both iterative methods avoid call stack overflow risks that the recursive approach faces with deep trees.
Comparison of all three approaches:
| Approach | Time | Space | Stack-safe? | Traversal Order |
|---|---|---|---|---|
| Recursive | O(n) | O(h) | No (deep trees risk overflow) | Depth-first |
| Iterative Stack | O(n) | O(n) | Yes | Depth-first |
| Iterative Queue | O(n) | O(n) | Yes | Breadth-first (level-order) |