π― JavaScript Trees β Top 25 Interview Questions
Q1 β Maximum Depth of Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Height = 1 + max(left, right); base case null β 0.
Step 2 β Brute force (what NOT to do): BFS counting levels β works but more code.
Step 3 β Optimal insight: Single DFS recursion; return value bubbles up the max depth.
Step 4 β Algorithm:
1. If node null β return 0
2. Recurse left and right
3. Return 1 + Math.max(left, right)
Step 5 β Edge cases: Empty tree β 0; single node β 1.
function maxDepth(root) {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
Time: O(n) | Space: O(h)
Q2 β Invert Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Swap left/right children at every node recursively.
Step 2 β Brute force (what NOT to do): Rebuild the tree β wasteful.
Step 3 β Optimal insight: Swap in-place then recurse.
Step 4 β Algorithm:
1. If null β return null
2. Swap left and right
3. Recurse on both
Step 5 β Edge cases: Null root; single node.
function invertTree(root) {
if (!root) return null;
[root.left, root.right] = [root.right, root.left];
invertTree(root.left);
invertTree(root.right);
return root;
}
Time: O(n) | Space: O(h)
Q3 β Symmetric Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Mirror check β l.left mirrors r.right, l.right mirrors r.left.
Step 2 β Brute force (what NOT to do): Inorder palindrome check β fails for some cases.
Step 3 β Optimal insight: Recursive helper comparing two subtrees.
Step 4 β Algorithm:
1. isMirror(l, r): both null β true; one null β false
2. vals equal AND isMirror(l.left, r.right) AND isMirror(l.right, r.left)
Step 5 β Edge cases: Single node β true.
function isSymmetric(root) {
return isMirror(root?.left, root?.right);
}
function isMirror(l, r) {
if (!l && !r) return true;
if (!l || !r) return false;
return l.val === r.val && isMirror(l.left, r.right) && isMirror(l.right, r.left);
}
Time: O(n) | Space: O(h)
Q4 β Path Sum
π§ How to Approach This
Step 1 β Recognize the pattern: Subtract node value at each step; check leaf when remainder = 0.
Step 2 β Brute force (what NOT to do): Enumerate all paths then sum β extra space.
Step 3 β Optimal insight: Pass remaining target down; at leaf check rem === 0.
Step 4 β Algorithm:
1. If null β false
2. rem = targetSum - root.val
3. If leaf and rem === 0 β true
4. return hasPathSum(left, rem) || hasPathSum(right, rem)
Step 5 β Edge cases: Negative values allowed.
function hasPathSum(root, targetSum) {
if (!root) return false;
const rem = targetSum - root.val;
if (!root.left && !root.right) return rem === 0;
return hasPathSum(root.left, rem) || hasPathSum(root.right, rem);
}
Time: O(n) | Space: O(h)
Q5 β Level Order Traversal
π§ How to Approach This
Step 1 β Recognize the pattern: BFS with level-size tracking.
Step 2 β Brute force (what NOT to do): DFS with level markers β more complex.
Step 3 β Optimal insight: Process queue in batches of current level size.
Step 4 β Algorithm:
1. Init queue with root
2. While queue: capture size, process that many nodes, enqueue children
Step 5 β Edge cases: Empty tree β [].
function levelOrder(root) {
if (!root) return [];
const result = [], queue = [root];
while (queue.length) {
const size = queue.length, level = [];
for (let i = 0; i < size; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(level);
}
return result;
}
Time: O(n) | Space: O(w)
Q6 β Validate Binary Search Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Every node must be within (min, max) bounds.
Step 2 β Brute force (what NOT to do): Inorder check only β insufficient.
Step 3 β Optimal insight: Pass min/max bounds; tighten at each step.
Step 4 β Algorithm:
1. validate(node, min=-Inf, max=+Inf)
2. If val β€ min or val β₯ max β false
3. Recurse left with max=val, right with min=val
Step 5 β Edge cases: Integer boundary values.
function isValidBST(root, min = -Infinity, max = Infinity) {
if (!root) return true;
if (root.val <= min || root.val >= max) return false;
return isValidBST(root.left, min, root.val)
&& isValidBST(root.right, root.val, max);
}
Time: O(n) | Space: O(h)
Q7 β Lowest Common Ancestor of BST
π§ How to Approach This
Step 1 β Recognize the pattern: BST property narrows direction at each step.
Step 2 β Brute force (what NOT to do): General LCA ignoring BST property β O(n).
Step 3 β Optimal insight: Both < root β go left; both > root β go right; else root is LCA.
Step 4 β Algorithm:
1. If both p,q < root.val β recurse left
2. If both > root.val β recurse right
3. Else return root
Step 5 β Edge cases: p or q equals root.
function lowestCommonAncestor(root, p, q) {
if (p.val < root.val && q.val < root.val)
return lowestCommonAncestor(root.left, p, q);
if (p.val > root.val && q.val > root.val)
return lowestCommonAncestor(root.right, p, q);
return root;
}
Time: O(h) | Space: O(h)
Q8 β Lowest Common Ancestor of Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Post-order DFS; both sides non-null β current node is LCA.
Step 2 β Brute force (what NOT to do): Store root-to-node paths then compare.
Step 3 β Optimal insight: Single DFS returning node when found.
Step 4 β Algorithm:
1. If null or matches p/q β return node
2. Both sides non-null β return root
3. Else return whichever is non-null
Step 5 β Edge cases: p is ancestor of q.
function lcaBinaryTree(root, p, q) {
if (!root || root === p || root === q) return root;
const left = lcaBinaryTree(root.left, p, q);
const right = lcaBinaryTree(root.right, p, q);
return left && right ? root : (left || right);
}
Time: O(n) | Space: O(h)
Q9 β Binary Tree Right Side View
π§ How to Approach This
Step 1 β Recognize the pattern: BFS β take last node at each level.
Step 2 β Brute force (what NOT to do): DFS with level tracking β more complex.
Step 3 β Optimal insight: During BFS record the last node per level.
Step 4 β Algorithm:
1. BFS level-order
2. At i === size-1 β push to result
Step 5 β Edge cases: Left-skewed tree.
function rightSideView(root) {
if (!root) return [];
const result = [], queue = [root];
while (queue.length) {
const size = queue.length;
for (let i = 0; i < size; i++) {
const node = queue.shift();
if (i === size - 1) result.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
return result;
}
Time: O(n) | Space: O(w)
Q10 β Count Good Nodes in Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Track max on path; node is good if val >= max.
Step 2 β Brute force (what NOT to do): Store entire path array β extra space.
Step 3 β Optimal insight: Pass running max down; count at each good node.
Step 4 β Algorithm:
1. dfs(node, maxSoFar)
2. good = node.val >= maxSoFar ? 1 : 0
3. Recurse both children with updated max
Step 5 β Edge cases: Root is always good.
function goodNodes(root) {
function dfs(node, maxVal) {
if (!node) return 0;
const good = node.val >= maxVal ? 1 : 0;
const newMax = Math.max(maxVal, node.val);
return good + dfs(node.left, newMax) + dfs(node.right, newMax);
}
return dfs(root, -Infinity);
}
Time: O(n) | Space: O(h)
Q11 β Kth Smallest in BST
π§ How to Approach This
Step 1 β Recognize the pattern: Inorder of BST = sorted; stop at kth element.
Step 2 β Brute force (what NOT to do): Collect all values then index β O(n) space.
Step 3 β Optimal insight: Iterative inorder with early exit.
Step 4 β Algorithm:
1. Iterative inorder with stack
2. Decrement k on each pop; return when k === 0
Step 5 β Edge cases: k=1 (minimum); k=n (maximum).
function kthSmallest(root, k) {
const stack = [];
let curr = root;
while (curr || stack.length) {
while (curr) { stack.push(curr); curr = curr.left; }
curr = stack.pop();
if (--k === 0) return curr.val;
curr = curr.right;
}
return -1;
}
Time: O(h + k) | Space: O(h)
Q12 β Binary Tree Maximum Path Sum
π§ How to Approach This
Step 1 β Recognize the pattern: At each node, path through it = node + max(0,left) + max(0,right).
Step 2 β Brute force (what NOT to do): Try all node pairs β O(nΒ²).
Step 3 β Optimal insight: Post-order; track global max; return only one branch to parent.
Step 4 β Algorithm:
1. gain(node) = node.val + max(0, gain(left)) + max(0, gain(right)) β update max
2. Return node.val + max(0, max(gainL, gainR))
Step 5 β Edge cases: All negative values; single node.
function maxPathSum(root) {
let max = -Infinity;
function gain(node) {
if (!node) return 0;
const l = Math.max(0, gain(node.left));
const r = Math.max(0, gain(node.right));
max = Math.max(max, node.val + l + r);
return node.val + Math.max(l, r);
}
gain(root);
return max;
}
Time: O(n) | Space: O(h)
Q13 β Serialize and Deserialize Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Preorder DFS + null markers.
Step 2 β Brute force (what NOT to do): Level-order with position indices β complex.
Step 3 β Optimal insight: "val,#,#" format is self-describing; recurse on token list.
Step 4 β Algorithm:
1. Serialize: DFS preorder, "#" for null, join with ","
2. Deserialize: split, index pointer, recurse
Step 5 β Edge cases: Empty tree β "#"; single node β "1,#,#".
function serialize(root) {
if (!root) return '#';
return root.val + ',' + serialize(root.left) + ',' + serialize(root.right);
}
function deserialize(data) {
const tokens = data.split(',');
let idx = 0;
function build() {
const val = tokens[idx++];
if (val === '#') return null;
const node = new TreeNode(+val);
node.left = build();
node.right = build();
return node;
}
return build();
}
Time: O(n) | Space: O(n)
Q14 β Construct Tree from Preorder + Inorder
π§ How to Approach This
Step 1 β Recognize the pattern: preorder[0] is root; find in inorder to split subtrees.
Step 2 β Brute force (what NOT to do): Linear scan inorder at each level β O(nΒ²).
Step 3 β Optimal insight: Map inorder indices for O(1) lookup.
Step 4 β Algorithm:
1. Build index map from inorder
2. build(preL, preR, inL, inR): preorder[preL] is root
3. leftSize = rootIdx - inL; recurse
Step 5 β Edge cases: Single element arrays.
function buildTree(preorder, inorder) {
const map = new Map(inorder.map((v, i) => [v, i]));
function build(preL, preR, inL, inR) {
if (preL > preR) return null;
const rootVal = preorder[preL];
const node = new TreeNode(rootVal);
const rootIdx = map.get(rootVal);
const leftSize = rootIdx - inL;
node.left = build(preL + 1, preL + leftSize, inL, rootIdx - 1);
node.right = build(preL + leftSize + 1, preR, rootIdx + 1, inR);
return node;
}
return build(0, preorder.length - 1, 0, inorder.length - 1);
}
Time: O(n) | Space: O(n)
Q15 β Flatten Binary Tree to Linked List
π§ How to Approach This
Step 1 β Recognize the pattern: Preorder order; rewire right pointers, left = null.
Step 2 β Brute force (what NOT to do): Collect preorder in array, rewire β O(n) space.
Step 3 β Optimal insight: Find rightmost of left subtree, attach right there, move left to right.
Step 4 β Algorithm:
1. While curr: if left, find rightmost of left β attach curr.right
2. curr.right = curr.left; curr.left = null; advance
Step 5 β Edge cases: No left child.
function flatten(root) {
let curr = root;
while (curr) {
if (curr.left) {
let prev = curr.left;
while (prev.right) prev = prev.right;
prev.right = curr.right;
curr.right = curr.left;
curr.left = null;
}
curr = curr.right;
}
}
Time: O(n) | Space: O(1)
Q16 β Diameter of Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Diameter through node = height(left) + height(right).
Step 2 β Brute force (what NOT to do): Compute height per node separately β O(nΒ²).
Step 3 β Optimal insight: Combine height + diameter in one DFS.
Step 4 β Algorithm:
1. DFS returns height; update max with l+r
2. Return 1 + max(l, r) to parent
Step 5 β Edge cases: Path need not pass through root.
function diameterOfBinaryTree(root) {
let max = 0;
function depth(node) {
if (!node) return 0;
const l = depth(node.left);
const r = depth(node.right);
max = Math.max(max, l + r);
return 1 + Math.max(l, r);
}
depth(root);
return max;
}
Time: O(n) | Space: O(h)
Q17 β Balanced Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Return -1 as sentinel for unbalanced; otherwise height.
Step 2 β Brute force (what NOT to do): Compute height separately β O(nΒ²).
Step 3 β Optimal insight: Post-order DFS propagating -1 immediately.
Step 4 β Algorithm:
1. check(null) β 0
2. l=-1 β return -1; r=-1 β return -1
3. |l-r|>1 β return -1; else 1+max(l,r)
Step 5 β Edge cases: Empty tree β true.
function isBalanced(root) {
function check(node) {
if (!node) return 0;
const l = check(node.left); if (l === -1) return -1;
const r = check(node.right); if (r === -1) return -1;
if (Math.abs(l - r) > 1) return -1;
return 1 + Math.max(l, r);
}
return check(root) !== -1;
}
Time: O(n) | Space: O(h)
Q18 β Sum Root to Leaf Numbers
π§ How to Approach This
Step 1 β Recognize the pattern: Pass accumulated number down; add at leaf.
Step 2 β Brute force (what NOT to do): Store all paths as strings, parse.
Step 3 β Optimal insight: curr = prev * 10 + node.val; sum at leaf.
Step 4 β Algorithm:
1. dfs(node, curr=0): curr = curr*10 + val
2. If leaf β return curr
3. Return dfs(left, curr) + dfs(right, curr)
Step 5 β Edge cases: Single node β its value.
function sumNumbers(root) {
function dfs(node, curr) {
if (!node) return 0;
curr = curr * 10 + node.val;
if (!node.left && !node.right) return curr;
return dfs(node.left, curr) + dfs(node.right, curr);
}
return dfs(root, 0);
}
Time: O(n) | Space: O(h)
Q19 β Path Sum II (All Paths)
π§ How to Approach This
Step 1 β Recognize the pattern: Backtracking DFS β push, recurse, pop.
Step 2 β Brute force (what NOT to do): Copy array at each node β O(nΒ·h) space.
Step 3 β Optimal insight: Mutate single path array; push before recurse, pop after.
Step 4 β Algorithm:
1. dfs(node, rem, path, result)
2. Push val; if leaf and rem==0 β push copy
3. Recurse left/right; pop path
Step 5 β Edge cases: No valid paths β [].
function pathSum(root, targetSum) {
const result = [];
function dfs(node, rem, path) {
if (!node) return;
path.push(node.val);
rem -= node.val;
if (!node.left && !node.right && rem === 0) result.push([...path]);
dfs(node.left, rem, path);
dfs(node.right, rem, path);
path.pop();
}
dfs(root, targetSum, []);
return result;
}
Time: O(nΒ·h) | Space: O(nΒ·h)
Q20 β Convert Sorted Array to BST
π§ How to Approach This
Step 1 β Recognize the pattern: Mid element = root β balanced; recurse on halves.
Step 2 β Brute force (what NOT to do): Insert one-by-one β O(n log n), may be unbalanced.
Step 3 β Optimal insight: Divide-and-conquer with mid = (l+r) >> 1.
Step 4 β Algorithm:
1. build(l, r): if l > r return null
2. mid = (l+r)>>1; node
3. left = build(l, mid-1); right = build(mid+1, r)
Step 5 β Edge cases: Empty array β null.
function sortedArrayToBST(nums) {
function build(l, r) {
if (l > r) return null;
const mid = (l + r) >> 1;
const node = new TreeNode(nums[mid]);
node.left = build(l, mid - 1);
node.right = build(mid + 1, r);
return node;
}
return build(0, nums.length - 1);
}
Time: O(n) | Space: O(log n)
Q21 β Binary Tree Zigzag Level Order
π§ How to Approach This
Step 1 β Recognize the pattern: BFS + alternate direction each level.
Step 2 β Brute force (what NOT to do): Two separate queues β complex.
Step 3 β Optimal insight: BFS + toggle + reverse on odd levels.
Step 4 β Algorithm:
1. Standard BFS with level batches
2. Toggle ltr; if !ltr β level.reverse()
Step 5 β Edge cases: Single node.
function zigzagLevelOrder(root) {
if (!root) return [];
const result = [], queue = [root];
let ltr = true;
while (queue.length) {
const size = queue.length, level = [];
for (let i = 0; i < size; i++) {
const n = queue.shift();
level.push(n.val);
if (n.left) queue.push(n.left);
if (n.right) queue.push(n.right);
}
result.push(ltr ? level : level.reverse());
ltr = !ltr;
}
return result;
}
Time: O(n) | Space: O(w)
Q22 β Delete Node in BST
π§ How to Approach This
Step 1 β Recognize the pattern: Three cases: leaf, one child, two children (inorder successor).
Step 2 β Brute force (what NOT to do): Rebuild BST β O(n).
Step 3 β Optimal insight: Replace value with inorder successor, delete successor.
Step 4 β Algorithm:
1. Recurse to find key
2. Leaf β null; one child β return it
3. Two children: find min of right subtree, replace val, delete that node
Step 5 β Edge cases: Key not present; root deletion.
function deleteNode(root, key) {
if (!root) return null;
if (key < root.val) root.left = deleteNode(root.left, key);
else if (key > root.val) root.right = deleteNode(root.right, key);
else {
if (!root.left) return root.right;
if (!root.right) return root.left;
let succ = root.right;
while (succ.left) succ = succ.left;
root.val = succ.val;
root.right = deleteNode(root.right, succ.val);
}
return root;
}
Time: O(h) | Space: O(h)
Q23 β Binary Tree Level Order Bottom-Up
π§ How to Approach This
Step 1 β Recognize the pattern: Standard BFS level-order then reverse.
Step 2 β Brute force (what NOT to do): DFS with depth tracking and sort.
Step 3 β Optimal insight: BFS + result.reverse() at end.
Step 4 β Algorithm:
1. Standard BFS into result
2. Return result.reverse()
Step 5 β Edge cases: Empty tree β [].
function levelOrderBottom(root) {
if (!root) return [];
const result = [], queue = [root];
while (queue.length) {
const size = queue.length, level = [];
for (let i = 0; i < size; i++) {
const n = queue.shift();
level.push(n.val);
if (n.left) queue.push(n.left);
if (n.right) queue.push(n.right);
}
result.push(level);
}
return result.reverse();
}
Time: O(n) | Space: O(w)
Q24 β Recover Binary Search Tree
π§ How to Approach This
Step 1 β Recognize the pattern: Inorder traversal finds two out-of-order nodes.
Step 2 β Brute force (what NOT to do): Collect + sort + reinsert β O(n log n).
Step 3 β Optimal insight: Track prev in inorder; first inversion β first bad; second β second bad; swap vals.
Step 4 β Algorithm:
1. Inorder DFS tracking prev
2. First violation: first = prev, second = curr
3. Second violation: second = curr only
4. Swap first.val β second.val
Step 5 β Edge cases: Adjacent nodes swapped.
function recoverTree(root) {
let first = null, second = null, prev = null;
function inorder(node) {
if (!node) return;
inorder(node.left);
if (prev && prev.val > node.val) {
if (!first) first = prev;
second = node;
}
prev = node;
inorder(node.right);
}
inorder(root);
[first.val, second.val] = [second.val, first.val];
}
Time: O(n) | Space: O(h)
Q25 β Maximum Width of Binary Tree
π§ How to Approach This
Step 1 β Recognize the pattern: BFS with heap-style position indices; width = rightmost - leftmost + 1.
Step 2 β Brute force (what NOT to do): Track null nodes in queue β exponential space.
Step 3 β Optimal insight: Normalize each level's index to prevent overflow.
Step 4 β Algorithm:
1. BFS queue of [node, index] pairs
2. Per level: width = last.idx β first.idx + 1; normalize by subtracting first.idx
Step 5 β Edge cases: Single node per level β 1.
function widthOfBinaryTree(root) {
if (!root) return 0;
let maxWidth = 0;
let queue = [[root, 0]];
while (queue.length) {
const size = queue.length;
const offset = queue[0][1];
let first = 0, last = 0;
const next = [];
for (let i = 0; i < size; i++) {
const [node, idx] = queue[i];
const cur = idx - offset;
if (i === 0) first = cur;
if (i === size-1) last = cur;
if (node.left) next.push([node.left, 2 * cur]);
if (node.right) next.push([node.right, 2 * cur + 1]);
}
maxWidth = Math.max(maxWidth, last - first + 1);
queue = next;
}
return maxWidth;
}
Time: O(n) | Space: O(w)