Validate Binary Search Tree — Min/Max Bounds DFS
The seductive wrong answer, the correct min/max bounds approach, and how to defend it in an interview.
What you'll learn
- ✓Why checking only direct children fails
- ✓The min/max bounds DFS pattern
- ✓The inorder traversal alternative
- ✓Common pitfalls with duplicates and integer limits
Prerequisites
- •Tree node basics — see Binary Trees: Introduction
- •DFS traversals — see Binary Tree Traversals
Validate BST looks like a five-minute problem until you submit a five-minute solution and watch it fail. The trap is that “left is smaller, right is bigger” describes a local relationship, but a BST demands a global one. Every node has an implicit range it must live inside, and that range is the whole problem.
The Problem
Given the root of a binary tree, decide whether it is a valid binary search tree. A valid BST has:
- All nodes in the left subtree strictly less than the current node
- All nodes in the right subtree strictly greater than the current node
- Both subtrees themselves valid BSTs
The constraint that often gets missed: it must hold for every descendant, not just the immediate children.
Intuition
Pass a (low, high) range down. The root starts with (-inf, +inf). When you recurse left, the upper bound becomes the current value; when you recurse right, the lower bound does. Any node outside its range fails immediately. An equivalent angle: an inorder traversal of a BST is strictly increasing — walk inorder, track the previous value, and bail if anything is out of order.
Explanation with Example
Consider the tree [5, 1, 4, null, null, 3, 6]. Visually:
- Root 5 with range
(-inf, +inf)— passes. - Left child 1 with range
(-inf, 5)— passes. - Right child 4 with range
(5, +inf)— fails. 4 is not greater than 5.
The naive “check children only” version would have happily compared 4 to its children 3 and 6, and missed that 4 must also exceed 5.
5 range (-inf, +inf) OK
/ \
range 1 4 range (5, +inf) FAIL
(-inf, 5) / \
3 6
descent rule:
go left : high becomes node.val
go right : low becomes node.val Code
def isValidBST(root):
def dfs(node, low, high):
if not node:
return True
if node.val <= low or node.val >= high:
return False
return dfs(node.left, low, node.val) and dfs(node.right, node.val, high)
return dfs(root, float("-inf"), float("inf"))public boolean isValidBST(TreeNode root) {
return dfs(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean dfs(TreeNode node, long low, long high) {
if (node == null) return true;
if (node.val <= low || node.val >= high) return false;
return dfs(node.left, low, node.val) && dfs(node.right, node.val, high);
}bool dfs(TreeNode* node, long low, long high) {
if (!node) return true;
if (node->val <= low || node->val >= high) return false;
return dfs(node->left, low, node->val) && dfs(node->right, node->val, high);
}
bool isValidBST(TreeNode* root) {
return dfs(root, LONG_MIN, LONG_MAX);
}Edge Cases
- A single-node tree is valid.
- An empty tree is valid by convention.
- Duplicate values are not allowed — the comparisons must be strict.
- Integer extremes matter. If a node holds
2**31 - 1, using regular ints as sentinels is fine in Python but breaks naive C++ ports. UselongorNoneas sentinels. - A degenerate tree shaped like a linked list still works, but recursion may need an iterative variant for very deep trees.
Complexity Analysis
The optimal DFS visits each node once and does constant work, so time is O(n). Space is O(h) for the recursion stack, where h is tree height. A balanced tree gives O(log n), a skewed one O(n). The inorder iterative version has the same bounds. For why these matter, see Big-O Notation Explained.
How to Explain It in an Interview
Open with the trap: “Checking only immediate children is wrong because a node deep in the left subtree of the root must still be less than the root.” Then offer the fix: pass an allowed range down through DFS, tightening the upper bound on left moves and the lower bound on right moves. Mention the inorder traversal alternative as a sanity check — interviewers like seeing two angles. If asked about iterative versions, point at an explicit stack or Morris traversal.
Related Problems
- Kth Smallest Element in a BST (also leans on inorder)
- Lowest Common Ancestor of a BST
- Recover Binary Search Tree
- Convert Sorted Array to BST
All of them reward fluency with recursive thinking.
Wrap up
Validating a BST is a vocabulary check: do you know what “valid” really means across the whole tree? Once you see that every node has an implicit range, the DFS writes itself. Memorize the min/max bounds pattern — it shows up in nearly every BST verification or construction problem you will face.
Related articles
- DSA Kth Smallest Element in a BST — Iterative Inorder Early Exit
The inorder traversal trick, the iterative early-exit version, and the follow-up that haunts FAANG interviews.
- DSA Lowest Common Ancestor of a BST — One-Line Guided Walk
Why BST ordering collapses LCA into a one-line traversal, and the iterative version that needs zero extra space.
- DSA Binary Tree Level Order Traversal — Queue Size Snapshot
The queue-with-size BFS pattern, why DFS still works, and how this template extends to zigzag and right-side view.
- DSA Path Sum — DFS With a Running Target
The DFS-with-decrement trick, why the leaf condition is the whole problem, and the variants that build on it.