Validate Binary Search Tree

Medium
Solve this question on LeetCode

Given the root of a binary tree, determine if it is a valid binary search tree (BST). A valid BST is defined as follows: the left subtree of a node contains only nodes with keys less than the node's key, the right subtree contains only nodes with keys greater than the node's key, and both the left and right subtrees must also be binary search trees. All values in the tree must be strictly less or greater — equality with a parent invalidates the BST property.

Examples

Example 1:
Input: root = [2,1,3]
Output: true
Explanation: Node 2 is the root. Its left child is 1 (< 2) and right child is 3 (> 2). Both subtrees are single nodes, which are trivially valid BSTs. The entire tree satisfies the BST property.
Example 2:
Input: root = [5,1,4,null,null,3,6]
Output: false
Explanation: The root node's value is 5, but its right child has value 4. The node with value 4 is in the right subtree of 5, so it must be greater than 5 — but 4 < 5, violating the BST property. Even though 3 < 4 and 6 > 4 locally, the overall tree is invalid.
Constraints:
1 <= number of nodes <= 10^4
-2^31 <= Node.val <= 2^31 - 1

Algorithm Walkthrough

The most intuitive approach to validate a BST is to recursively verify that every node falls within a valid range defined by its ancestors. The key insight is that a node's value is not only constrained by its immediate parent — it is constrained by all ancestors above it. For example, if a node is in the right subtree of a node with value 5, and then in the left subtree of a node with value 10, that node must satisfy 5 < val < 10. A simple check that only compares a node with its immediate children would miss this inherited constraint.

The algorithm starts at the root with a range of (-infinity, +infinity), meaning the root can hold any value. As we traverse down the tree, we tighten these bounds. When we move to a left child, the parent's value becomes the new upper bound — every node in the left subtree must be less than the parent. When we move to a right child, the parent's value becomes the new lower bound — every node in the right subtree must be greater than the parent. At each node, we check if its value falls strictly within the current (low, high) range. If any node violates this range, the tree is not a valid BST and we return false immediately.

Let's trace through Example 2 to see why this range-tracking is essential. The tree is [5, 1, 4, null, null, 3, 6]. We start at root 5 with range (-inf, +inf). Since 5 is within range, we recurse left to node 1 with range (-inf, 5). Node 1 is valid, its children are null, so we return to node 5 and recurse right to node 4 with range (5, +inf). Now we check: is 4 > 5? No — 4 is not in the range (5, +inf). We return false immediately. Notice how the ancestor bound (5) from the root was carried down to the right subtree. A naive approach that only checks node.left.val < node.val < node.right.val would incorrectly see that 4 < 5 (locally true) and miss the violation.

An important edge case involves the boundary values of the integer type. Since node values can equal Integer.MIN_VALUE (-231) or Integer.MAX_VALUE (231 - 1), using integer types for the bounds can cause overflow or comparison failures. In Java, this is solved by using long with Long.MIN_VALUE and Long.MAX_VALUE as initial bounds. In Python, float('-inf') and float('inf') handle this naturally. In C++, passing nullptr for the initial bounds and checking against actual ancestor nodes avoids integer overflow entirely.

The base case is straightforward: a null node is considered a valid BST (vacuously true). The recursion naturally terminates when it reaches null leaves. The overall result is true only if every node in the tree passes its range check.

  1. Call validate(root, -inf, +inf) to start with an unrestricted range
  2. If the current node is null, return true (base case)
  3. If node.val <= low or node.val >= high, return false
  4. Recurse left with (low, node.val) and right with (node.val, high)
  5. Return true only if both subtrees are valid

Idea Map

Start

Call validate(root, -inf, +inf)

If node == null return true

If val <= low or val >= high return false

1. Left: recurse with (low, node.val)
2. Right: recurse with (node.val, high)
3. Return true if both are valid

Return result

Complexity Analysis

Time Complexity

O(n) - Each node in the tree is visited exactly once. At every node, the algorithm performs only a constant-time range check and passes updated bounds to its children. No hashing, scanning, or extra computation is needed per node, making the total time linear in the number of nodes.

Space Complexity

O(h) - The recursion call stack depth is determined by the height h of the tree. For a balanced tree, h = O(log n). In the worst case of a completely skewed tree, h = O(n). No additional data structures are used beyond the recursion stack.

Code


import math
Import the math module to access `math.inf` and `math.neg_inf` for representing unbounded initial range limits.
def isValidBST(root):
Defines the main function that takes the root of a binary tree and returns True if it is a valid BST, False otherwise.
def validate(node, low=-math.inf, high=math.inf):
Defines the recursive helper with default bounds of negative and positive infinity. The root starts unconstrained; bounds tighten as we descend. Every node must satisfy `low < node.val < high`.
if not node:
Base case: a null (empty) node is trivially a valid BST. This also terminates recursion at leaf children.
return True
Return True for null nodes. An empty subtree does not violate any BST property.
if node.val <= low or node.val >= high:
Check if the current node's value violates its allowed range. Values must be strictly within (low, high) — equal to either bound means the BST property is broken. Using `<=` and `>=` enforces strict inequality.
return False
If the node's value is outside the valid range, immediately return False — no need to check further since the tree is already invalid.
return (validate(node.left, low, node.val) and
Recurse into the left subtree with the upper bound tightened to `node.val`. All left descendants must be strictly less than this node.
validate(node.right, node.val, high))
Recurse into the right subtree with the lower bound tightened to `node.val`. All right descendants must be strictly greater than this node. Both subtrees must be valid.
return validate(root)
Kick off the recursion from the root with default bounds (-inf, +inf). Returns True only if every node in the entire tree passes its range check.

public boolean isValidBST(TreeNode root) {
Public entry point that takes the root of a binary tree and returns true if it is a valid BST. Delegates to the private validate helper with long bounds.
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
Start validation with the widest possible range using `long` type. `Long.MIN_VALUE` and `Long.MAX_VALUE` safely bound any `int` node value, including Integer.MIN_VALUE and Integer.MAX_VALUE.
}
End of the isValidBST method.
private boolean validate(TreeNode node, long low, long high) {
Private recursive helper that checks if a subtree rooted at `node` is a valid BST within the range (low, high). Using `long` parameters prevents overflow when comparing with int node values.
if (node == null) return true;
Base case: null nodes are valid. This terminates recursion at leaf children and handles empty subtrees.
if (node.val <= low || node.val >= high) return false;
Check if the node's value violates the allowed range. The comparison with `long` bounds works correctly even for Integer.MIN_VALUE and Integer.MAX_VALUE because `long` has a wider range.
return validate(node.left, low, node.val) &&
Recurse left with the upper bound tightened to `node.val`. All left descendants must be strictly less than this node. `&&` short-circuits: if left is false, right is not checked.
validate(node.right, node.val, high);
Recurse right with the lower bound tightened to `node.val`. All right descendants must be strictly greater than this node. Both calls must return true for the tree to be valid.
}
End of the validate helper method.

class Solution {
Defines the Solution class with a private recursive validate helper and a public entry point.
public:
Public access specifier for the LeetCode-required entry point method.
bool isValidBST(TreeNode* root) {
Public entry point that takes the root of a binary tree and returns true if it is a valid BST. Passes nullptr for both bounds to start with an unrestricted range.
return validate(root, nullptr, nullptr);
Start validation with null bounds, meaning no constraints. Instead of using numeric infinity (which risks overflow with int), we pass actual ancestor TreeNode pointers and compare values directly.
}
End of the isValidBST method.
private:
Private access specifier for the recursive helper method.
bool validate(TreeNode* node, TreeNode* low, TreeNode* high) {
Private recursive helper. Uses TreeNode pointers for bounds instead of integer values — if `low` is null there is no lower bound, if `high` is null there is no upper bound. This elegantly avoids integer overflow issues.
if (!node) return true;
Base case: null nodes are valid BSTs. Terminates recursion at leaf children.
if (low && node->val <= low->val) return false;
Check the lower bound: if `low` is not null and the current node's value is less than or equal to the low bound's value, the BST property is violated. Returns false immediately.
if (high && node->val >= high->val) return false;
Check the upper bound: if `high` is not null and the current node's value is greater than or equal to the high bound's value, the BST property is violated.
return validate(node->left, low, node) &&
Recurse left with the current node as the new upper bound. All left descendants must be less than this node. Short-circuit `&&` avoids unnecessary right subtree checks.
validate(node->right, node, high);
Recurse right with the current node as the new lower bound. All right descendants must be greater than this node.
}
End of the validate helper method.
};
End of the Solution class.

Edge Cases & Common Mistakes

⚠️

Node values can equal Integer.MIN_VALUE or Integer.MAX_VALUE

In Java, using `Integer.MIN_VALUE` and `Integer.MAX_VALUE` as initial bounds fails when a node actually holds those values. For example, if the root is Integer.MIN_VALUE and the initial low bound is also Integer.MIN_VALUE, the check `node.val > low` would fail incorrectly. Always use `long` (Long.MIN_VALUE, Long.MAX_VALUE) in Java to provide a wider range that safely encompasses all possible int values. Python handles this naturally with `math.inf` and `-math.inf`.

💡

Inorder traversal of a valid BST produces a strictly increasing sequence

An alternative approach is to perform an inorder traversal and verify that each value is strictly greater than the previous one. This works because inorder traversal of a BST visits nodes in ascending order. If any value is not greater than the previous, the tree is invalid. This approach also runs in O(n) time and O(h) space, but requires tracking the previous value via a mutable reference or instance variable.

Range-based recursive approach is clean and handles all edge cases

The range-based approach is the most straightforward solution. It naturally tracks all ancestor constraints without needing extra state beyond the recursive parameters. It handles single-node trees, skewed trees, and trees with extreme values correctly. The short-circuit evaluation (`&&`) ensures early termination as soon as an invalid node is found, making it efficient even on large invalid trees.

Understanding Binary Search Trees

Binary search trees are one of the most fundamental data structures in computer science. Understanding BST properties — including how insertion, deletion, and validation work — is essential for technical interviews and real-world applications like database indexing and balanced tree implementations.

Learn more about understanding BST properties and validation →
lightbulb

Disclaimer: This problem is for educational purposes. Practice the 'Validate Binary Search Tree' problem on LeetCode.