โ† Back to DevBytes

Solving Symmetric Tree in Python: Step-by-Step Guide

Solving Symmetric Tree in Python: A Step-by-Step Guide

The Symmetric Tree problem is one of the most popular binary tree challenges you will encounter in coding interviews and on platforms like LeetCode. At its core, the problem asks a deceptively simple question: given the root of a binary tree, is the tree a mirror reflection of itself around its center? While the definition is easy to grasp, implementing an efficient solution requires a solid understanding of tree traversal techniques, recursion, and iterative approaches using queues. This tutorial walks you through everything you need to know, from the underlying concepts to production-ready Python code.

What Is a Symmetric Tree?

A binary tree is considered symmetric when its left and right subtrees are mirror images of each other. Imagine folding the tree vertically along the root node; if every node on the left side aligns perfectly with a corresponding node on the right side, the tree is symmetric. This mirror property must hold at every level of the tree, not just the top.

Formally, two subtrees are mirrors of each other if:

Consider the following example of a symmetric tree:

        1
       / \
      2   2
     / \ / \
    3  4 4  3

This tree is symmetric because each pair of mirrored nodes matches in value and structure. Now compare it with an asymmetric tree:

        1
       / \
      2   2
       \   \
       3    3

Here, the left child of the left node is missing while the right child of the right node is missing, so the mirror property fails.

Why the Symmetric Tree Problem Matters

Beyond being a common interview question, the Symmetric Tree problem teaches several foundational concepts that apply broadly in software engineering. First, it reinforces recursive thinking, which is essential for working with hierarchical data structures like file systems, DOM trees, and JSON documents. Second, it demonstrates how to translate a recursive solution into an iterative one using a queue, a skill that helps when recursion depth becomes a concern. Finally, it sharpens your ability to reason about structural equality, a concept that appears in diffing algorithms, AST comparison, and configuration validation.

In practical applications, symmetry checks are useful in computer graphics for procedural mesh generation, in game development for level validation, and in data engineering for verifying balanced schemas. Mastering this problem gives you a mental model for comparing nested structures in general.

Defining the Tree Node

Before writing any solution, we need a class to represent a binary tree node. In Python, this is typically defined as a simple class with a value and two child pointers.

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

This definition is identical to the one used by LeetCode, so solutions written against it will port directly to online judges. The default arguments allow you to construct leaf nodes and empty children without extra boilerplate.

Approach 1: Recursive Solution

The recursive approach is the most intuitive way to solve the problem. The key insight is to treat the tree as two subtrees rooted at the left and right children of the root, then check whether those two subtrees are mirrors of each other. We define a helper function that accepts two nodes and verifies the mirror conditions.

The algorithm works as follows:

def isSymmetric(root):
    if root is None:
        return True

    def isMirror(left, right):
        if left is None and right is None:
            return True
        if left is None or right is None:
            return False
        return (
            left.val == right.val
            and isMirror(left.left, right.right)
            and isMirror(left.right, right.left)
        )

    return isMirror(root.left, root.right)

Let us trace through the symmetric example from earlier. The root has value 1, so we call isMirror on its two children, both with value 2. Their values match, so we recurse on the outer pair (3, 3) and the inner pair (4, 4). Each of those pairs matches in value and has no children, so every recursive call returns True, and the final result is True.

The time complexity is O(n), where n is the number of nodes, because we visit each node exactly once. The space complexity is O(h), where h is the height of the tree, due to the recursion stack. In the worst case of a skewed tree, this becomes O(n), but for a balanced tree it is O(log n).

Approach 2: Iterative Solution Using a Queue

While recursion is elegant, it can cause a stack overflow on very deep trees. The iterative approach avoids this by using a queue to process node pairs level by level. Instead of relying on the call stack, we explicitly enqueue pairs of nodes that should be mirrors of each other.

The algorithm proceeds as follows:

from collections import deque

def isSymmetricIterative(root):
    if root is None:
        return True

    queue = deque()
    queue.append(root.left)
    queue.append(root.right)

    while queue:
        left = queue.popleft()
        right = queue.popleft()

        if left is None and right is None:
            continue
        if left is None or right is None:
            return False
        if left.val != right.val:
            return False

        queue.append(left.left)
        queue.append(right.right)
        queue.append(left.right)
        queue.append(right.left)

    return True

This version has the same O(n) time complexity, but the space complexity is now governed by the queue rather than the call stack. In the worst case, the queue holds up to O(n) nodes, which happens when the tree is wide near the top. Using deque from the collections module ensures that popleft is an O(1) operation, which would not be the case with a regular list.

Testing Your Solution

A robust solution deserves thorough testing. You should cover symmetric trees, asymmetric trees, single-node trees, empty trees, and trees that are symmetric in value but not in structure. Here is a small test harness you can run locally.

def build_symmetric_tree():
    #        1
    #       / \
    #      2   2
    #     / \ / \
    #    3  4 4  3
    return TreeNode(
        1,
        TreeNode(2, TreeNode(3), TreeNode(4)),
        TreeNode(2, TreeNode(4), TreeNode(3))
    )

def build_asymmetric_tree():
    #        1
    #       / \
    #      2   2
    #       \   \
    #        3   3
    return TreeNode(
        1,
        TreeNode(2, None, TreeNode(3)),
        TreeNode(2, None, TreeNode(3))
    )

if __name__ == "__main__":
    symmetric = build_symmetric_tree()
    asymmetric = build_asymmetric_tree()
    single = TreeNode(1)
    empty = None

    print(isSymmetric(symmetric))      # True
    print(isSymmetric(asymmetric))     # False
    print(isSymmetric(single))         # True
    print(isSymmetric(empty))          # True

    print(isSymmetricIterative(symmetric))   # True
    print(isSymmetricIterative(asymmetric))  # False
    print(isSymmetricIterative(single))      # True
    print(isSymmetricIterative(empty))       # True

Running this script should print True, False, True, True for each implementation. These cases exercise the base conditions, the value mismatch path, and the structural mismatch path, giving you confidence that both implementations behave identically.

Best Practices

When implementing the Symmetric Tree solution, keep the following best practices in mind to write clean, maintainable, and efficient code.

Common Pitfalls

Even experienced developers can stumble on this problem. One frequent mistake is comparing the left subtree with the left subtree and the right subtree with the right subtree, which checks for equality rather than mirror symmetry. Remember that mirror comparison always pairs the left child of one node with the right child of the other.

Another pitfall is forgetting to enqueue children in the correct order in the iterative solution. If you enqueue left.left with right.left, you are checking for identical subtrees instead of mirrored ones. The correct pairing is left.left with right.right and left.right with right.left.

Finally, be careful with mutable default arguments. Although the TreeNode constructor uses None as the default for left and right, which is safe, using a mutable object like a list as a default argument would cause shared state across instances. Stick with None and construct children explicitly.

Conclusion

The Symmetric Tree problem is a compact exercise that packs in recursion, tree traversal, and queue-based iteration. By understanding the mirror property and implementing both a recursive and an iterative solution, you gain a versatile toolkit for comparing hierarchical structures in Python. The recursive approach is concise and expressive, while the iterative approach offers safety against deep recursion and demonstrates how to transform any depth-first traversal into a breadth-first one. With the code, tests, and best practices covered in this guide, you are well equipped to solve this problem confidently in interviews and apply the same patterns to broader challenges involving structural comparison.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles