← Back to DevBytes

Diameter of Binary Tree: Multiple Solutions and Complexity Analysis

What Is the Diameter of a Binary Tree?

The diameter of a binary tree (also called the width) is the number of nodes on the longest path between any two leaf nodes in the tree. This path may or may not pass through the root. In other words, it is the maximum distance between any two nodes, measured in edges (some definitions count nodes instead—be sure to clarify which convention you're using).

Formally, for any node, the diameter is the maximum value of:

And the overall tree diameter is the maximum of this value across all nodes in the tree.

Visual Example

Consider this tree (counting edges):


        1
       / \
      2   3
     / \
    4   5
   /     \
  6       7

Longest path: from node 6 to node 7 — path: 6 → 4 → 2 → 5 → 7 (4 edges).
Diameter = 4 edges (or 5 nodes, depending on convention).

Why the Diameter Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Understanding and computing the diameter of a binary tree is fundamental in several areas:

Solution 1: Naive O(n²) Approach

The simplest approach computes the diameter by calculating, for every node, the sum of left and right subtree heights. This leads to repeated work because heights are recomputed independently for each node.

Algorithm

  1. For each node, compute the height of its left subtree.
  2. Compute the height of its right subtree.
  3. Let current_diameter = left_height + right_height (edges) or + 1 (nodes).
  4. Update the global maximum diameter.
  5. Recursively repeat for left and right children.

Code (Python, counting edges)


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

def height(node):
    """Returns the height of a node (edges from node to deepest leaf)."""
    if node is None:
        return -1  # so leaf has height 0
    return 1 + max(height(node.left), height(node.right))

def diameter_of_tree_naive(root):
    """Naive O(n²) diameter calculation."""
    if root is None:
        return 0

    # diameter passing through this node
    left_h = height(root.left)
    right_h = height(root.right)
    current_diam = left_h + right_h  # edges

    # diameters in left and right subtrees
    left_diam = diameter_of_tree_naive(root.left)
    right_diam = diameter_of_tree_naive(root.right)

    return max(current_diam, left_diam, right_diam)

# Example usage
# Construct the tree from the visual example above
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.left.left.left = TreeNode(6)
root.left.right.right = TreeNode(7)

print(diameter_of_tree_naive(root))  # Output: 4

Complexity Analysis

This approach works correctly but is inefficient. For a balanced tree of 10⁵ nodes, it performs billions of operations — completely impractical for production.

Solution 2: Optimized O(n) Single-Pass DFS

We can avoid the O(n²) pitfall by computing both height and diameter in a single bottom-up traversal. The key insight: as we compute heights recursively, we can simultaneously update the diameter.

The Core Idea

For each node, the recursion returns its height. While computing left and right heights, we calculate the candidate diameter left_height + right_height and update a global (or passed-by-reference) maximum. This way, each node is visited exactly once.

Approach A: Global Variable


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

class Solution:
    def diameter_of_tree(self, root):
        self.max_diameter = 0
        self._compute_height(root)
        return self.max_diameter

    def _compute_height(self, node):
        if node is None:
            return -1  # leaf gets height 0 (edges)

        left_h = self._compute_height(node.left)
        right_h = self._compute_height(node.right)

        # Update diameter (edges)
        self.max_diameter = max(self.max_diameter, left_h + right_h)

        # Return height of this node
        return 1 + max(left_h, right_h)

# Example
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.left.left.left = TreeNode(6)
root.left.right.right = TreeNode(7)

s = Solution()
print(s.diameter_of_tree(root))  # Output: 4

Approach B: Returning a Pair (Height, Diameter)

Instead of a global variable, you can return a tuple containing both the height and the best diameter found so far. This is a functional-style approach preferred in some languages.


def diameter_and_height(node):
    """Returns (diameter, height) for the subtree rooted at node."""
    if node is None:
        return (0, -1)  # diameter=0, height=-1 (edges)

    left_diam, left_h = diameter_and_height(node.left)
    right_diam, right_h = diameter_and_height(node.right)

    # Current node's height
    current_h = 1 + max(left_h, right_h)

    # Diameter candidates: best from left, best from right, and via current node
    current_diam = max(
        left_diam,
        right_diam,
        left_h + right_h
    )

    return (current_diam, current_h)

def diameter_of_tree_pair(root):
    if root is None:
        return 0
    diam, _ = diameter_and_height(root)
    return diam

Complexity Analysis (Both Optimized Approaches)

This is the gold standard solution used in interviews and production code.

Solution 3: Iterative Post-Order Using a Stack

In environments where recursion depth may cause stack overflow (e.g., very deep trees in languages with limited recursion), an iterative solution is valuable. We simulate post-order traversal using an explicit stack and a visited marker.

Algorithm

  1. Push each node with a flag indicating whether it has been visited (or use two stacks).
  2. When popping a node that's been visited, compute its height using the already-computed heights of its children (stored in a hash map).
  3. Update the diameter as left_h + right_h.
  4. Store the computed height for this node.

Code (Python)


def diameter_iterative(root):
    if root is None:
        return 0

    max_diameter = 0
    heights = {None: -1}  # Sentinel for null children
    stack = [(root, False)]  # (node, visited_flag)

    while stack:
        node, visited = stack.pop()

        if node is None:
            continue

        if visited:
            # Children heights are already computed
            left_h = heights.get(node.left, -1)
            right_h = heights.get(node.right, -1)

            # Update diameter
            max_diameter = max(max_diameter, left_h + right_h)

            # Store this node's height
            heights[node] = 1 + max(left_h, right_h)
        else:
            # Push node back with visited=True
            stack.append((node, True))
            # Push children (right first so left processes first — order doesn't matter here)
            if node.right:
                stack.append((node.right, False))
            if node.left:
                stack.append((node.left, False))

    return max_diameter

Alternative: Using Two Stacks for Cleaner Logic


def diameter_iterative_two_stacks(root):
    if root is None:
        return 0

    max_diameter = 0
    heights = {None: -1}
    post_order_stack = []
    traversal_stack = [root]

    # First, get post-order sequence
    while traversal_stack:
        node = traversal_stack.pop()
        post_order_stack.append(node)
        if node.left:
            traversal_stack.append(node.left)
        if node.right:
            traversal_stack.append(node.right)

    # Process in reverse (post-order)
    while post_order_stack:
        node = post_order_stack.pop()
        left_h = heights.get(node.left, -1)
        right_h = heights.get(node.right, -1)
        max_diameter = max(max_diameter, left_h + right_h)
        heights[node] = 1 + max(left_h, right_h)

    return max_diameter

Complexity Analysis (Iterative)

Solution 4: BFS Level-Order for Special Cases

While a general BFS cannot compute diameter directly (since diameter depends on subtree heights), for a complete binary tree or when you already have parent pointers and leaf nodes, you can approximate or compute the diameter using level-order traversal combined with additional logic. This is more of a niche approach, included here for completeness.

When This Makes Sense


# Example: Diameter of a perfect binary tree (edges)
def diameter_perfect_tree(height):
    """For a perfect binary tree of given height (edges), diameter = 2 * height."""
    return 2 * height

# For level-based calculation with parent pointers
def diameter_with_parent_pointers(nodes, parent_map):
    """
    Given a list of leaf nodes and a dict mapping node->parent,
    compute the longest path between any two leaves.
    This is essentially the tree diameter.
    """
    from collections import defaultdict
    
    # Build adjacency list
    adj = defaultdict(list)
    for node, parent in parent_map.items():
        if parent is not None:
            adj[node].append(parent)
            adj[parent].append(node)
    
    # Run two BFS passes to find diameter in the undirected tree
    # (Standard tree diameter algorithm for general graphs)
    def bfs(start):
        visited = set()
        queue = [(start, 0)]
        farthest_node, max_dist = start, 0
        while queue:
            curr, dist = queue.pop(0)  # O(n) naive pop — use deque in production
            if curr in visited:
                continue
            visited.add(curr)
            if dist > max_dist:
                farthest_node, max_dist = curr, dist
            for neighbor in adj[curr]:
                if neighbor not in visited:
                    queue.append((neighbor, dist + 1))
        return farthest_node, max_dist
    
    # Pick an arbitrary node (any leaf works)
    start = nodes[0]
    farthest, _ = bfs(start)
    _, diameter = bfs(farthest)
    return diameter

This two-BFS method works on any tree treated as an undirected graph, but it's O(n) with higher constant factors than the DFS solution. Use it when you don't have the tree structure in memory as linked nodes but rather as an adjacency representation.

Edge Cases and Common Pitfalls

Empty Tree

If the tree is empty (root is None), the diameter is 0. Always handle this base case first to avoid null-pointer errors.

Single Node Tree

A tree with exactly one node has diameter 0 (edges) or 1 (nodes). Make sure your height function returns the correct sentinel: -1 for edges so that left_h + right_h yields 0, or 0 for nodes yielding 1.

Skewed (Degenerate) Tree

For a tree that is essentially a linked list (all left children or all right children), the diameter equals the height of the tree (number of edges from root to leaf). The recursive stack can blow up here — consider the iterative solution.

Negative Heights

When using -1 as the height of None, leaf nodes correctly get height 0. This is the most common convention and avoids off-by-one errors.

Best Practices

Production-Ready Template (Python)


from typing import Optional

class TreeNode:
    def __init__(self, val: int = 0, left: 'Optional[TreeNode]' = None, right: 'Optional[TreeNode]' = None):
        self.val = val
        self.left = left
        self.right = right

def diameter_of_binary_tree(root: Optional[TreeNode]) -> int:
    """
    Returns the diameter of a binary tree measured in edges.
    Uses O(n) time and O(h) space.
    """
    max_diam = 0

    def height(node: Optional[TreeNode]) -> int:
        nonlocal max_diam
        if node is None:
            return -1
        left_h = height(node.left)
        right_h = height(node.right)
        max_diam = max(max_diam, left_h + right_h)
        return 1 + max(left_h, right_h)

    height(root)
    return max_diam

Comparative Complexity Table

Solution Time Complexity Space Complexity Use Case
Naive (separate height calls) O(n²) O(h) Educational only — do not use
Optimized DFS (global/tuple) O(n) O(h) Best general solution
Iterative post-order stack O(n) O(n) Deep trees, avoids recursion limit
Two-BFS (graph diameter) O(n) O(n) When tree is an adjacency graph

Conclusion

The diameter of a binary tree is a deceptively simple problem that elegantly demonstrates how a small algorithmic insight—computing heights and diameter simultaneously—can transform an O(n²) naive solution into an optimal O(n) one. The single-pass DFS approach remains the most widely used and taught method, striking the perfect balance between clarity and efficiency. For production systems with extremely deep trees, the iterative stack variant provides a robust fallback. Regardless of which implementation you choose, the core takeaway is the same: never recompute what you can propagate upward in a single traversal. Master this pattern, and you'll be well-equipped for similar tree problems like finding the maximum path sum, checking tree balance, or computing the minimum height.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles