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:
- Left height + right height for that node (if counting edges)
- Or left height + right height + 1 (if counting nodes)
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:
- Network topology analysis: In routing protocols, the diameter represents the worst-case hop count between any two nodes, impacting latency guarantees.
- Database indexing: B-tree and binary search tree structures benefit from minimal diameter for faster lookups.
- Social network graphs: When trees model hierarchical relationships, the diameter gives the longest chain of communication.
- Competitive programming: This is a classic interview and contest problem that tests recursive thinking and optimization skills.
- Real-world systems: File system hierarchies, DOM trees, and organizational charts all use diameter-like metrics for performance profiling.
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
- For each node, compute the height of its left subtree.
- Compute the height of its right subtree.
- Let
current_diameter = left_height + right_height(edges) or+ 1(nodes). - Update the global maximum diameter.
- 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
- Time Complexity: O(n²) in the worst case (skewed tree). Each call to
height()takes O(n), and we call it for every node during the diameter recursion. - Space Complexity: O(h) for the recursion stack, where h is the height of the tree. In worst case (skewed), O(n).
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)
- Time Complexity: O(n) — every node is visited exactly once. At each node, we do O(1) work (two max operations).
- Space Complexity: O(h) for the recursion stack. In the worst case (skewed tree), this becomes O(n). For a balanced tree, O(log n).
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
- Push each node with a flag indicating whether it has been visited (or use two stacks).
- When popping a node that's been visited, compute its height using the already-computed heights of its children (stored in a hash map).
- Update the diameter as
left_h + right_h. - 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)
- Time Complexity: O(n) — each node is pushed and popped a constant number of times.
- Space Complexity: O(n) for the stack and the heights dictionary. This is slightly higher than the recursive version's implicit stack, but avoids stack overflow for deep trees.
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
- You already have leaf node information and parent pointers (like in a tree stored with node-to-parent mappings).
- You want the diameter measured in terms of levels (not edges), which corresponds to the height difference.
- You are dealing with a perfect binary tree where the diameter is trivially
2 * height.
# 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
- Clarify the convention: Always specify whether you're counting edges or nodes. In interviews, state it explicitly. Most online judges (LeetCode, HackerRank) count edges.
- Use the single-pass DFS: It's the most efficient and clean solution. The global variable pattern or tuple-return pattern are both fine; choose based on language idioms.
- Avoid recomputation: Never call a standalone
height()inside a diameter loop — this is the classic O(n²) trap. - Handle deep trees: For trees deeper than ~1000 nodes, Python's default recursion limit may bite you. Either increase
sys.setrecursionlimitor use the iterative stack approach. - Test thoroughly: Include tests for empty tree, single node, balanced tree, skewed tree, and trees where the diameter doesn't pass through the root.
- Use type hints: In production Python, annotate functions with
Optional[TreeNode]and return types for clarity.
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.