2025, Oct 16 15:00
Binary Tree Equality: Optimal Recursive Comparison with Identity Checks vs Iterative Alternatives
Learn how to compare binary trees for exact structural and value equality: optimal O(n) time, O(h) space. See recursive code with identity checks and iterative.
Comparing two binary trees for structural and value equality looks like a problem where clever tricks could squeeze out extra performance. In practice, the straightforward recursive solution is already as fast as it gets, and with a small refinement it is both elegant and efficient.
Problem setup
The task is to check whether two binary trees are identical in both shape and node values. A typical recursive approach visits nodes in lockstep and short-circuits on the first mismatch. The time complexity is O(n) because every node may need to be examined once, and the auxiliary space is O(h), where h is the height of the tree.
class Node:
    def __init__(self, val=None, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
def same_tree(u: Node, v: Node) -> bool:
    if u is None and v is None:
        return True
    return (u is not None and v is not None) and (u.val == v.val) and \
        same_tree(u.left, v.left) and same_tree(u.right, v.right)
x = Node(1)
y = Node(1)
print(same_tree(x, y))
What is really going on
No algorithm can be faster than O(n) here because you must at least look at each node once to be certain the trees match. That lower bound makes the recursive solution theoretically optimal on time. On memory, the recursion uses O(h) stack frames, which is optimal for a depth-first comparison that follows the tree shape. Short-circuiting ensures the traversal stops as soon as a difference is found.
There is a small refinement worth adding. An identity check avoids deeper work when both references point to the exact same node object, and an early negative check keeps the hot path clear for mismatches.
Optimized recursive version
def trees_match(a, b):
    if a is b:
        return True
    if not a or not b or a.val != b.val:
        return False
    return trees_match(a.left, b.left) and trees_match(a.right, b.right)
This preserves O(n) time and O(h) space. The identity test handles both the None case and identical references immediately. The early return on structural or value mismatch short-circuits the work as soon as possible. In practice, this avoids unnecessary calls and keeps the code lean without adding bookkeeping overhead.
Are there faster or more memory-efficient alternatives
Alternatives based on explicit stacks or queues are viable, but they do not beat the asymptotics. An iterative depth-first or breadth-first traversal also runs in O(n) time. It trades the call stack for a heap-allocated stack or queue, so the auxiliary space profile shifts but remains proportional to the traversal frontier. There is no speed miracle to be found; the main difference is operational style and where the state lives.
There are practical considerations that influence the choice. One view is that the recursive form is clean, has minimal overhead, and tends to have better cache locality by naturally following the tree structure. Another view highlights reliability concerns around recursion depth in real systems and favors an iterative routine that will not depend on call stack limits. Both can be true depending on your constraints.
A function that requires you to evaluate and adjust the size of your call stack is far less useful than one that just works reliably. In real life, people will just call it and expect it to work; otherwise it becomes a landmine that explodes when you least expect it.
If you are worried about reaching the recursion limit, that can be set to practically infinite.
The iterative version is preferable when you do not know for certain that your trees are shallow. In production code you would not recursively process a binary tree unless it is balanced like a red-black tree or AVL tree.
These perspectives frame a simple rule of thumb. If your trees are balanced or known to be shallow, the recursive approach is both optimal and easy to maintain. If tree depth is unknown or can grow very large, an iterative routine avoids surprises tied to recursion depth.
Why this matters
Tree comparisons sit on hot paths in many systems. Knowing that O(n) is the ceiling stops you from chasing nonexistent speedups, and recognizing the O(h) stack behavior lets you choose between recursion and iteration based on operational risk, not guesswork. The tiny improvements in the recursive version, like identity checks and early exits, deliver practical wins without complicating the code.
Takeaways
Use the recursive solution with early exits when you control or can bound tree depth. It is already optimal on time, space-efficient for balanced trees, and pleasantly concise. Switch to an iterative stack or queue when depth is unbounded or when stack limits are a concern. Either way, expect O(n) time; that is not just good enough, it is the best you can do for exact structural and value equality.
The article is based on a question from StackOverflow by Jared McCarthy and an answer by TensorMind.