DSA · Binary Trees

Binary Tree Interview Problems: Traversals, Views, Diameter and Path Sums

Every binary tree question is a traversal plus a decision about what each node returns to its parent. Learn the traversals recursively and iteratively, then apply them to views, diameter, maximum path sum and path-sum problems.

✍️ Pranjal Jain, Ex-Microsoft · IIT Kanpur 📅 10 October 2026 ⏱ 16 min read
Glowing binary tree with a highlighted maximum path and light beams showing tree views
TL;DR
  • Every tree problem is "traverse, and decide what each node returns to its parent". Most answers are a postorder DFS that combines left and right results, sometimes updating a global best.
  • Know all three DFS orders recursively and iteratively (stack), plus BFS level order (queue). Iterative inorder and postorder are common follow-ups.
  • Views: left/right view key on depth; top/bottom view key on horizontal distance and need BFS (or DFS tracking depth).
  • Diameter and maximum path sum share one trick: return the best single-branch value upward, but update the answer with the two-branch value at each node.

Binary trees are probably the most-asked topic after arrays and strings at product companies, and they are where recursion either clicks or does not. The good news: tree questions are far more uniform than they first look. Once you can answer "what does this function return for a subtree, and how do I combine left and right?", most of them become 10 lines of code.

This guide builds that skill in order: traversals (recursive, iterative and level order), then views, then the "combine children" problems (height, balance, diameter, maximum path sum), then path sums and construction. If recursion itself feels shaky, read recursion for beginners first.

The node definition and the example tree used in dry runs throughout:

Java
class TreeNode {
    int val;
    TreeNode left, right;
    TreeNode(int val) { this.val = val; }
}
Python
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val, self.left, self.right = val, left, right
1 / \ 2 3 / \ \ 4 5 6

The Mental Model: What Does Each Node Return?

For any tree problem, decide which of three shapes it has:

ShapeInformation flowExamples
Top-down (preorder)Parent passes state down as parametersRoot-to-leaf path sum, max depth with a depth parameter, right view via DFS, good nodes count
Bottom-up (postorder)Children return values, parent combinesHeight, balanced check, diameter, max path sum, LCA, subtree sums
Level by level (BFS)Queue processes one depth at a timeLevel order, zigzag, right view, minimum depth, max width

The bottom-up shape has one key refinement: the value a node returns to its parent can differ from the value it contributes to the answer. Diameter and maximum path sum are the famous examples. Spotting that distinction is what interviewers are testing.

Complexity is nearly always O(n) time, because each node is visited once, and O(h) space for recursion, where h is the height: O(log n) for a balanced tree and O(n) for a skewed one. State the skewed worst case; it matters for stack overflow on deep trees.

DFS Traversals: Preorder, Inorder, Postorder

OrderVisit sequenceExample tree outputTypical use
PreorderNode, left, right1 2 4 5 3 6Copying/serialising a tree, top-down state
InorderLeft, node, right4 2 5 1 3 6Sorted order in a BST
PostorderLeft, right, node4 5 2 6 3 1Deleting trees, computing subtree values
Level orderDepth by depth[1], [2, 3], [4, 5, 6]Level-based questions
Python (recursive)
def preorder(root, out):
    if root:
        out.append(root.val)
        preorder(root.left, out)
        preorder(root.right, out)

def inorder(root, out):
    if root:
        inorder(root.left, out)
        out.append(root.val)
        inorder(root.right, out)

def postorder(root, out):
    if root:
        postorder(root.left, out)
        postorder(root.right, out)
        out.append(root.val)

Recursive versions are trivial; the follow-up "now do it without recursion" is where marks are earned.

Iterative Tree Traversals With a Stack

Iterative preorder

Pop a node, visit it, push right then left (so left is processed first).

Python
def preorder_iterative(root):
    if not root:
        return []
    out, stack = [], [root]
    while stack:
        node = stack.pop()
        out.append(node.val)
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)
    return out

Iterative inorder

Go left as far as possible, pushing nodes; pop, visit, then move to the right child.

Java
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    Deque<TreeNode> stack = new ArrayDeque<>();
    TreeNode cur = root;
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) { stack.push(cur); cur = cur.left; }   // slide left
        cur = stack.pop();
        res.add(cur.val);                                          // visit
        cur = cur.right;                                           // then right subtree
    }
    return res;
}

Dry run on the example tree: push 1, 2, 4; pop 4 (visit), right is null; pop 2 (visit), go to 5; push 5, pop 5 (visit); pop 1 (visit), go to 3; push 3, pop 3 (visit), go to 6; push 6, pop 6 (visit). Output 4 2 5 1 3 6. This template is the basis of BST iterators and "kth smallest in a BST" (see BST problems).

Iterative postorder

Two common approaches. The easy one: do a modified preorder (node, right, left) and reverse the output, which gives left, right, node. The "true" one-stack version tracks the last visited node so you know whether you are returning from the right subtree.

Java (one stack)
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    Deque<TreeNode> stack = new ArrayDeque<>();
    TreeNode cur = root, lastVisited = null;
    while (cur != null || !stack.isEmpty()) {
        while (cur != null) { stack.push(cur); cur = cur.left; }
        TreeNode top = stack.peek();
        if (top.right != null && top.right != lastVisited) {
            cur = top.right;                     // right subtree not done yet
        } else {
            res.add(top.val);                    // both subtrees done
            lastVisited = stack.pop();
        }
    }
    return res;
}
Python (reverse trick)
def postorder_iterative(root):
    if not root:
        return []
    out, stack = [], [root]
    while stack:
        node = stack.pop()
        out.append(node.val)           # node, right, left ...
        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)
    return out[::-1]                   # ... reversed = left, right, node

Morris traversal: O(1) extra space

Morris inorder temporarily threads each node's inorder predecessor back to it, then removes the thread. O(n) time, O(1) space, and the tree is restored. It is a "do you know it exists" question more than a "code it" question, but here is the core:

Python
def morris_inorder(root):
    out, cur = [], root
    while cur:
        if not cur.left:
            out.append(cur.val)
            cur = cur.right
        else:
            pred = cur.left
            while pred.right and pred.right is not cur:
                pred = pred.right
            if not pred.right:          # create thread, go left
                pred.right = cur
                cur = cur.left
            else:                       # thread exists: left done, remove it
                pred.right = None
                out.append(cur.val)
                cur = cur.right
    return out

BFS: Level Order, Zigzag and Width

The key detail in level-order BFS is capturing the queue size at the start of each level, so you know exactly which nodes belong to it.

Python
from collections import deque

def level_order(root):
    if not root:
        return []
    res, q = [], deque([root])
    while q:
        level = []
        for _ in range(len(q)):        # exactly the nodes of this level
            node = q.popleft()
            level.append(node.val)
            if node.left:
                q.append(node.left)
            if node.right:
                q.append(node.right)
        res.append(level)
    return res
Java
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> res = new ArrayList<>();
    if (root == null) return res;
    Queue<TreeNode> q = new ArrayDeque<>();
    q.offer(root);
    while (!q.isEmpty()) {
        int size = q.size();
        List<Integer> level = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            TreeNode node = q.poll();
            level.add(node.val);
            if (node.left != null) q.offer(node.left);
            if (node.right != null) q.offer(node.right);
        }
        res.add(level);
    }
    return res;
}

Variants that reuse this loop: zigzag order (reverse every other level, or use a deque and insert at the front on odd levels); average of levels; minimum depth (return the depth of the first leaf found, which BFS finds earliest); maximum width (store each node's position index, children at 2i and 2i + 1, width = last - first + 1, and normalise per level to avoid overflow); and connecting next-right pointers.

Binary Tree Views: Left, Right, Top and Bottom

Views are standard in Indian interview prep sheets (GeeksforGeeks, Striver) and frequently asked at service and product companies alike. The trick is choosing the right key.

Left and right view: key on depth

Right view is the last node of each BFS level (LeetCode 199). With DFS, visit right before left and record the first node seen at each new depth.

Python
def right_side_view(root):
    res = []
    def dfs(node, depth):
        if not node:
            return
        if depth == len(res):          # first node seen at this depth
            res.append(node.val)
        dfs(node.right, depth + 1)     # right first for the right view
        dfs(node.left, depth + 1)
    dfs(root, 0)
    return res

On the example tree: right view 1 3 6, left view (swap the two recursive calls) 1 2 4. Note a subtle case: if the right subtree is shorter, deeper nodes from the left subtree appear in the right view, which this code handles automatically.

Top and bottom view: key on horizontal distance

Assign the root horizontal distance (hd) 0; a left child gets hd - 1 and a right child hd + 1. Top view is the first node seen per hd in BFS order; bottom view is the last. BFS is used because it visits shallower nodes first; a plain DFS would get the depth ordering wrong unless it also tracks depth.

Java
public List<Integer> topView(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    TreeMap<Integer, Integer> firstAtHd = new TreeMap<>();        // sorted by hd
    Queue<Object[]> q = new ArrayDeque<>();
    q.offer(new Object[]{root, 0});
    while (!q.isEmpty()) {
        Object[] cur = q.poll();
        TreeNode node = (TreeNode) cur[0];
        int hd = (int) cur[1];
        firstAtHd.putIfAbsent(hd, node.val);     // bottom view: use put() instead
        if (node.left != null) q.offer(new Object[]{node.left, hd - 1});
        if (node.right != null) q.offer(new Object[]{node.right, hd + 1});
    }
    res.addAll(firstAtHd.values());
    return res;
}

On the example tree, horizontal distances are: 4 at -2, 2 at -1, 1 and 5 at 0, 3 at +1, 6 at +2. Top view: 4 2 1 3 6. Bottom view: 4 2 5 3 6, since 5 is deeper than 1 at hd 0. You can replace the TreeMap with a hashmap plus tracking min and max hd for O(n) instead of O(n log n).

Vertical Order Traversal (LeetCode 987) generalises this: group nodes by hd, and within a column sort by depth, then by value for ties. The tie-breaking rule is the part candidates miss.

Height, Balanced Check and Diameter

Maximum depth

depth(node) = 1 + max(depth(left), depth(right)), with depth(null) = 0. The canonical postorder function.

Balanced binary tree (LeetCode 110)

The naive version calls height at every node: O(n2) on a skewed tree. Return height and validity in one pass by using -1 as an "unbalanced" sentinel.

Java
public boolean isBalanced(TreeNode root) { return check(root) != -1; }

private int check(TreeNode node) {
    if (node == null) return 0;
    int l = check(node.left);
    if (l == -1) return -1;                      // short-circuit
    int r = check(node.right);
    if (r == -1 || Math.abs(l - r) > 1) return -1;
    return 1 + Math.max(l, r);
}

Diameter of a binary tree (LeetCode 543)

The longest path between any two nodes may not pass through the root. For each node, the longest path that bends there has length height(left) + height(right) edges. Return the height upward; update a global with the sum.

Python
def diameter_of_binary_tree(root):
    best = 0
    def height(node):
        nonlocal best
        if not node:
            return 0
        l, r = height(node.left), height(node.right)
        best = max(best, l + r)         # path bending at this node, in edges
        return 1 + max(l, r)            # what the parent can extend
    height(root)
    return best

Example tree: at node 1, left height 2 (via 2 to 4) and right height 2 (via 3 to 6), so the diameter is 4 edges: 4-2-1-3-6. Interviewers often ask "edges or nodes?"; clarify, since the node count is one more.

Binary Tree Maximum Path Sum (LeetCode 124)

This Hard is diameter with values and negatives. A path can start and end anywhere but cannot branch. Define gain(node) as the best sum of a downward path starting at node. A negative child gain is never worth taking, so clamp it to 0.

  • Returned upward: node.val + max(leftGain, rightGain) (one branch only, since the parent will extend it).
  • Answer candidate: node.val + leftGain + rightGain (the path bends here).
Java
class Solution {
    private int best = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        gain(root);
        return best;
    }

    private int gain(TreeNode node) {
        if (node == null) return 0;
        int l = Math.max(gain(node.left), 0);    // drop negative branches
        int r = Math.max(gain(node.right), 0);
        best = Math.max(best, node.val + l + r);
        return node.val + Math.max(l, r);
    }
}
Python
def max_path_sum(root):
    best = float("-inf")
    def gain(node):
        nonlocal best
        if not node:
            return 0
        l = max(gain(node.left), 0)
        r = max(gain(node.right), 0)
        best = max(best, node.val + l + r)
        return node.val + max(l, r)
    gain(root)
    return best

Dry run on [-10, 9, 20, null, null, 15, 7] (root -10, left 9, right 20 with children 15 and 7): gain(9) = 9, best 9. gain(15) = 15, gain(7) = 7. At 20: candidate 20 + 15 + 7 = 42, returns 35. At -10: candidate -10 + 9 + 35 = 34, best stays 42. Answer 42.

⚠️
Two classic bugsInitialising best to 0 fails for an all-negative tree like [-3] (the answer is -3). Returning node.val + l + r upward lets the parent extend a path that already branched, which is not a valid path.

Path Sum Problems: Root-to-Leaf and Any Downward Path

Path Sum I and II (LeetCode 112, 113)

Top-down: pass the remaining sum down; at a leaf, check whether it is zero. For II, collect the path with backtracking (append before recursing, pop after) and copy the list when recording.

Python
def path_sum_ii(root, target):
    res, path = [], []
    def dfs(node, remaining):
        if not node:
            return
        path.append(node.val)
        remaining -= node.val
        if not node.left and not node.right and remaining == 0:
            res.append(path[:])             # copy, or later pops will mutate it
        dfs(node.left, remaining)
        dfs(node.right, remaining)
        path.pop()                          # backtrack
    dfs(root, target)
    return res

Note the leaf check: a node with one child is not a leaf, so [1, 2] with target 1 has no valid path. Candidates frequently get that wrong by checking node is None instead.

Path Sum III: any downward path (LeetCode 437)

Count paths going downward (not necessarily from root or to a leaf) summing to target. Brute force starts a DFS from every node: O(n2) on skewed trees. The O(n) solution is prefix sums on the root-to-node path, the same idea as subarray sum equals K, with backtracking to remove the current prefix when leaving a subtree.

Java
public int pathSum(TreeNode root, int targetSum) {
    Map<Long, Integer> prefixCount = new HashMap<>();
    prefixCount.put(0L, 1);
    return dfs(root, 0L, targetSum, prefixCount);
}

private int dfs(TreeNode node, long sum, int target, Map<Long, Integer> cnt) {
    if (node == null) return 0;
    sum += node.val;
    int paths = cnt.getOrDefault(sum - target, 0);
    cnt.merge(sum, 1, Integer::sum);
    paths += dfs(node.left, sum, target, cnt) + dfs(node.right, sum, target, cnt);
    cnt.merge(sum, -1, Integer::sum);          // backtrack: leave this path
    return paths;
}

Using long avoids overflow with large node values, which LeetCode's tests do include. The array version of this idea is explained in the hashing guide.

Other path problems

  • Sum Root to Leaf Numbers (129): pass current * 10 + val down.
  • Longest Univalue Path (687): diameter-style, but a child extends only if its value matches.
  • Lowest Common Ancestor (236): postorder returning the found node; see LCA explained.
  • All nodes at distance K (863): convert to a graph with parent pointers, then BFS.

Build, Compare and Transform Trees

Construct from preorder and inorder (LeetCode 105)

The first preorder element is the root; its position in inorder splits left and right subtrees. A hashmap from value to inorder index makes each split O(1), giving O(n) overall instead of O(n2).

Python
def build_tree(preorder, inorder):
    idx = {v: i for i, v in enumerate(inorder)}
    pre = iter(preorder)

    def build(lo, hi):                   # inorder range [lo, hi]
        if lo > hi:
            return None
        val = next(pre)
        node = TreeNode(val)
        mid = idx[val]
        node.left = build(lo, mid - 1)   # left first: matches preorder
        node.right = build(mid + 1, hi)
        return node

    return build(0, len(inorder) - 1)

This requires distinct values; say so. Preorder plus postorder alone cannot uniquely determine a general binary tree (only a full one), a common theory follow-up.

Quick ones that appear as warm-ups

  • Invert Binary Tree (226): swap children at every node, any traversal.
  • Same Tree (100) and Symmetric Tree (101): recursive pairwise comparison; for symmetric, compare left.left with right.right and left.right with right.left.
  • Subtree of Another Tree (572): same-tree check at every node, O(m·n), or serialise both and use string matching for linear time.
  • Flatten Binary Tree to Linked List (114): reverse postorder (right, left, node) with a prev pointer.
  • Serialize and Deserialize (297): preorder with null markers; see the full walkthrough.

Binary Tree Practice List

GroupProblems (LeetCode number unless noted)
TraversalsInorder (94), Preorder (144), Postorder (145), Level Order (102), Zigzag Level Order (103), Average of Levels (637)
ViewsRight Side View (199), Left View (GfG), Top View (GfG), Bottom View (GfG), Vertical Order Traversal (987), Maximum Width (662)
Bottom-upMaximum Depth (104), Minimum Depth (111), Balanced Binary Tree (110), Diameter (543), Maximum Path Sum (124), Longest Univalue Path (687)
PathsPath Sum (112), Path Sum II (113), Path Sum III (437), Sum Root to Leaf Numbers (129), All Nodes Distance K (863)
StructureInvert (226), Same Tree (100), Symmetric (101), Subtree of Another Tree (572), Construct from Preorder and Inorder (105), Flatten to Linked List (114), LCA (236), Serialize and Deserialize (297)

Binary Tree Interview Tips

  1. Clarify the definitions: diameter in edges or nodes? Path must start at root or end at leaf? Values can be negative? Duplicates allowed?
  2. Say the return contract out loud: "this function returns the height of the subtree, and updates the global diameter". It is the sentence that shows you understand the recursion.
  3. Handle null first. The base case defines the whole recursion: height(null) = 0, gain(null) = 0.
  4. Dry run on a three-node tree and a skewed tree. Skewed trees expose O(n2) mistakes and stack-depth issues.
  5. Give space as O(h) and explain h ranges from log n to n. If asked about very deep trees, offer the iterative version.
  6. Prefer locals or a small result object over static fields in Java; static state leaks between test cases on online judges and looks careless in interviews.
💡
Next stepBinary search trees add an ordering invariant on top of everything here, which unlocks O(h) search, inorder-sorted tricks and validation problems. Continue with BST interview problems, then graphs in BFS and DFS problems, which generalise these traversals.

Frequently Asked Questions

What are the most common binary tree interview questions?▾
Inorder, preorder, postorder and level order traversals (recursive and iterative), maximum depth, balanced tree check, diameter, invert tree, symmetric tree, left and right view, top and bottom view, lowest common ancestor, path sum variants, maximum path sum, serialize and deserialize, and constructing a tree from preorder and inorder traversals.
How do I do inorder traversal without recursion?▾
Use an explicit stack. Push nodes while going left, then pop a node, visit it, and move to its right child. Repeat until both the current pointer is null and the stack is empty. It runs in O(n) time and O(h) space, where h is the tree height.
What is the difference between DFS and BFS on trees?▾
DFS goes deep along one branch before backtracking and comes in three orders: preorder, inorder and postorder; it uses recursion or a stack with O(h) space. BFS visits level by level using a queue, with O(w) space where w is the maximum width. Use BFS for level-based questions like right view or minimum depth.
How is the diameter of a binary tree calculated?▾
For every node, the longest path passing through it is the height of its left subtree plus the height of its right subtree. A single postorder pass computes each height and updates a global maximum with left plus right, giving O(n) time. Diameter is usually counted in edges.
How does binary tree maximum path sum work?▾
A postorder DFS returns, for each node, the best downward path sum starting at that node, ignoring negative child contributions by clamping them to zero. At each node it also updates a global answer with node value plus both clamped child gains, because the best path may bend at that node.
What is the difference between top view and left view of a binary tree?▾
Left view shows the first node at each depth level when looking from the left side. Top view shows the first node at each horizontal distance from the root when looking from above, where going left subtracts one and going right adds one. They need different keys: depth for side views, horizontal distance for top and bottom views.

Make Tree Recursion Second Nature

Prepflix's DSA program builds tree intuition step by step with live dry runs and mock interviews that test the exact follow-ups covered here.

Explore the Prepflix Course →
Pranjal Jain
Pranjal Jain

Ex-Microsoft SDE · IIT Kanpur · Founder of Prepflix. Helps engineers crack startup and product company interviews across India.