Trees

Binary Search Tree Fundamentals

A binary tree with one additional constraint β€” the BST property: for every node, all values in its left subtree are strictly less and all values in its right subtree are strictly greater. This ordering invariant transforms O(n) linear search into O(log n) binary search on a linked structure.

Foundations Β· Array Β· Linked List Β· Stack Β· Queue Β· HashMap Β· Binary Tree Β· BST Β· Heap Β· Trie Β· Graph Β· Sorting Β· Binary Search Β· Two Pointers Β· Sliding Window Β· Dynamic Prog.
01
Section One Β· Foundation

What is Binary Search Tree?

8 3 10 1 6 9 14 < 8 > 8 < 3 > 3 < 10 > 10 BST property: left < node < right at every level

The ordering constraint that separates a binary search tree from a general binary tree is a single invariant applied recursively: every node's value is strictly greater than all values in its left subtree and strictly less than all values in its right subtree, making the entire structure a sorted collection navigable by comparison alone. That invariant buys logarithmic elimination β€” every comparison at a node discards an entire subtree, halving the remaining candidates at each step and reducing search from O(n) linear scan to O(log n) binary search without requiring the data to be stored in a contiguous array. Beyond search, the ordering produces a remarkable side effect: inorder traversal of a BST visits nodes in ascending sorted order, making it the only tree structure where a standard traversal yields sorted output as a natural byproduct. The hidden danger is that a BST is only O(log n) when balanced β€” inserting already-sorted data produces a degenerate tree of height nβˆ’1 that degrades every operation to O(n), which is why self-balancing variants like AVL and Red-Black trees exist. In Java, TreeMap and TreeSet use red-black trees β€” a self-balancing BST β€” internally, giving O(log n) put, get, contains, floor, ceiling, and range operations that make sorted collections practical at scale.

Analogy: A dictionary where every page is a decision point β€” the word you seek is either on this page, in the left half (alphabetically before), or in the right half (alphabetically after). At each page you eliminate half the remaining book. That is a binary search tree: every node is a comparison that routes you left or right, and the ordering constraint guarantees you are always moving toward your target β€” never backward, never sideways. A sorted dictionary works because of its ordering invariant; a BST works for exactly the same reason.
02
Section Two Β· Mental Model

How It Thinks

Every comparison eliminates an entire subtree
β–Ά
Search costs O(log n) on a balanced BST β€” the same binary search as on a sorted array, but on a linked structure
The BST property holds at every node, not just the root
β–Ά
Validation cannot check only the root's children β€” it must propagate min/max bounds through the entire tree top-down
Inorder traversal visits left subtree before root before right
β–Ά
Inorder traversal of a BST always yields sorted ascending output β€” the sorted array view of the BST is free and always up to date
Insertion always adds a new leaf β€” never restructures existing nodes
β–Ά
Insert follows the search path until null and places the node there β€” no rotations, no rebalancing in a plain BST
Inserting already-sorted data appends to one side only
β–Ά
Sorted insertion creates a degenerate right-skewed chain β€” height becomes O(n) and all operations degrade to O(n)
Deletion with two children cannot remove the node directly
β–Ά
The node must be replaced with its inorder successor (leftmost node in the right subtree) to preserve the BST property
03
Section Three Β· BST Property

The BST Property β€” The Ordering Invariant

The BST property is the single rule that makes a binary tree searchable: for every node N, every value in the left subtree of N is strictly less than N's value, and every value in the right subtree of N is strictly greater β€” this invariant must hold not just for direct children but for every node in both subtrees. The most common interview mistake is checking only direct children (left.val < node.val < right.val) and missing violations where a node's value is correct relative to its parent but wrong relative to a distant ancestor β€” the classic "subtree violation" that requires propagating bounds through the entire tree.

Valid BST vs Invalid BST β€” ancestor violation
VALID BST 8 3 10 1 6 9 14 3 < 8 βœ“ 10 > 8 βœ“ 1 < 3 βœ“ 6 > 3 βœ“ INVALID BST 8 3 10 1 9 9 > 3 locally OK βœ“ BUT 9 > 8 β€” violates ancestor bound βœ—
Validating the BST Property
Pseudocode
function isValidBST(node, min, max): if node is null: return true if node.val <= min or node.val >= max: return false // value outside allowed range return isValidBST(node.left, min, node.val) // tighten max and isValidBST(node.right, node.val, max) // tighten min // O(n) β€” every node checked once // Initial call: isValidBST(root, -∞, +∞)
Validation β€” bound propagation on the invalid BST
8 bounds: (-∞, +∞) 8 in range βœ“ 3 bounds: (-∞, 8) 3 in range βœ“ 10 bounds: (8, +∞) 10 in range βœ“ 1 bounds: (-∞, 3) 1 in range βœ“ 9 bounds: (3, 8) 9 β‰₯ 8 β€” INVALID βœ— max becomes 8 min becomes 8 max becomes 3 min becomes 3, max stays 8
Check min/max bounds, not parent comparison:
  • The naive check (node.left.val < node.val < node.right.val) fails on ancestor violations β€” a node can satisfy its parent's constraint while violating a grandparent's.
  • The correct approach passes inherited bounds [min, max] top-down and rejects any node whose value falls outside its allowed range.
  • This is the standard O(n) single-pass BST validation.
04
Section Four Β· Operations

Core Operations

Search
Compares the target value with the current node and recurses into the left subtree if smaller, the right subtree if larger, or returns the current node if equal β€” eliminating one subtree at every step.
Pseudocode
function search(node, target): if node is null: return null // not found if target == node.val: return node if target < node.val: return search(node.left, target) // go left else: return search(node.right, target) // go right // O(h): O(log n) balanced, O(n) degenerate
Search β€” finding value 6 in the BST
8 3 10 1 6 9 14 6 < 8 β†’ go left 6 > 3 β†’ go right 6 == 6 β†’ FOUND βœ“ Found in 3 comparisons β€” eliminated 4 nodes without visiting them
Search on a BST is binary search β€” not linear scan:
  • Each comparison eliminates an entire subtree.
  • On a balanced tree of 1,000,000 nodes, search visits at most 20 nodes (logβ‚‚ 1,000,000 β‰ˆ 20).
  • The iterative version (while loop, no recursion) is preferred in practice β€” it avoids call stack overhead and is easier to reason about for very deep trees.
Insert
Follows the search path to find where the new value belongs β€” the position where search would return null β€” and places a new leaf node there, preserving the BST property without restructuring.
Pseudocode
function insert(node, val): if node is null: return new TreeNode(val) // insert here as a new leaf if val < node.val: node.left = insert(node.left, val) else if val > node.val: node.right = insert(node.right, val) // val == node.val: duplicate β€” ignore (or handle per requirements) return node // O(h): O(log n) balanced, O(n) degenerate
Insert β€” adding value 5 to the BST
BEFORE β€” inserting 5 8 3 10 1 6 9 14 5 < 6 β†’ go left β†’ null AFTER β€” 5 inserted as leaf 8 3 10 1 6 9 14 5 NEW
Insert always creates a new leaf β€” no restructuring:
  • Unlike arrays (which require shifting) or balanced BSTs (which require rotations), a plain BST insert always adds a new leaf at the exact position where the search terminated.
  • The return-node pattern (returning node at each recursive call) cleanly handles the parent pointer update without a separate parent reference.
Min / Max / Floor / Ceiling
BST structure makes extremes and neighbors O(h) β€” min is the leftmost node, max is the rightmost node, floor is the largest value ≀ target, ceiling is the smallest value β‰₯ target.
Pseudocode
function findMin(node): while node.left != null: node = node.left return node // leftmost node function findMax(node): while node.right != null: node = node.right return node // rightmost node function floor(node, target): if node is null: return null if node.val == target: return node if node.val > target: return floor(node.left, target) // must go left // node.val < target β€” this node is a candidate, but right might be closer rightFloor = floor(node.right, target) return rightFloor if rightFloor != null else node // O(h) function ceiling(node, target): if node is null: return null if node.val == target: return node if node.val < target: return ceiling(node.right, target) // must go right leftCeiling = ceiling(node.left, target) return leftCeiling if leftCeiling != null else node // O(h)
Floor and ceiling exploit BST ordering β€” O(h) not O(n):
  • On a sorted array, floor and ceiling require binary search β€” O(log n).
  • On a BST, the same logic works by following left or right pointers: any node smaller than the target is a floor candidate, but the right subtree might have a closer candidate.
  • Java's TreeMap exposes this via floorKey(key) and ceilingKey(key), both O(log n).
Complexity Reference
Operation Balanced BST Degenerate BST Notes
Search O(log n) O(n) Follows one path root-to-leaf
Insert O(log n) O(n) Always adds new leaf
Delete O(log n) O(n) 3 cases; see Section 7
Find Min/Max O(log n) O(n) Go left/right until null
Floor/Ceiling O(log n) O(n) BST-ordering exploitation
Inorder O(n) O(n) Must visit all nodes
Validate BST O(n) O(n) Must check all nodes
05
Section Five Β· Traversals

Traversals on a BST β€” Why Inorder Is Special

All four traversal orders from the Binary Tree page work identically on a BST β€” the BST property does not change how traversal works, but it fundamentally changes what inorder traversal produces: unlike a general binary tree where inorder output is arbitrary, inorder traversal of a BST always yields elements in ascending sorted order. This sorted-output guarantee is not incidental β€” it is a direct consequence of the BST invariant (left < root < right at every node) and makes inorder traversal the solution to any problem requiring sorted extraction, kth smallest, or in-place BST sorting.

Inorder traversal β€” sorted output from a BST
1 1 3 2 6 3 8 4 9 5 10 6 14 7 β†’ inorder traversal β†’ Inorder output β€” always sorted on a BST 1 3 6 8 9 10 14 Guarantee: inorder(BST) = sorted(BST values) β€” always
BST Traversal Use Cases
Traversal Produces Common BST Use Case
Inorder Sorted ascending output Kth smallest, sorted iteration, BST sort
Preorder Root-first serialisation Reconstruct BST from array (O(n) with BST property)
Postorder Children-first aggregation Delete all nodes, size/height computation
Level-Order Level-by-level Minimum depth, BST level sums
Kth Smallest Element
Returns the kth smallest value in the BST β€” the kth node visited in inorder traversal.
Pseudocode
function kthSmallest(node, k): // Iterative inorder β€” stop at kth visit stack = empty stack count = 0 curr = node while curr != null or stack not empty: while curr != null: push(stack, curr) curr = curr.left curr = pop(stack) count++ if count == k: return curr.val // found kth smallest curr = curr.right // O(h + k) β€” not O(n)
Kth smallest stops early β€” O(h + k) not O(n):
  • The iterative inorder approach stops as soon as the kth node is visited β€” it does not traverse the entire tree.
  • Cost is O(h) to reach the leftmost node plus O(k) to count up to k β€” significantly faster than full traversal when k is small.
  • The recursive version requires extra state (a counter passed by reference or a class field) to stop early β€” the iterative version is cleaner for this problem.
06
Section Six Β· Variants

BST Variants β€” When Balance Matters

A plain BST offers O(log n) operations only when balanced β€” the insertion order of the data determines the tree's shape, and inserting sorted data degrades every operation to O(n) by creating a degenerate chain; self-balancing BST variants solve this by performing rotations after insert and delete to maintain a height bound of O(log n) regardless of insertion order. For interviews, understanding that AVL trees and Red-Black trees are BSTs with automatic rebalancing is sufficient β€” you will not be asked to implement rotations, but you will be asked why Java's TreeMap is O(log n) for all inputs while a plain BST can degrade to O(n).

Balanced BST vs Degenerate BST β€” same values, different insertion order
BALANCED BST Inserting: 4, 2, 6, 1, 3, 5, 7 4 2 6 1 3 5 7 height = 2 O(log n) per operation DEGENERATE BST Inserting: 1, 2, 3, 4, 5, 6, 7 1 2 3 4 5 … height = nβˆ’1 = 6 O(n) per operation Same 7 values β€” insertion ORDER determines the tree's shape and performance
BST Variants Comparison
Variant Balance Guarantee Height Rotations Java Class
Plain BST None O(n) worst None Custom TreeNode
AVL Tree |h(L)βˆ’h(R)| ≀ 1 O(log n) strict Yes (LL,RR,LR,RL) None built-in
Red-Black Tree ← Java Longest path ≀ 2Γ— shortest O(log n) Yes (color flip + rotation) TreeMap / TreeSet
Splay Tree Amortised O(log n) O(n) worst Yes (splay) None built-in
Treap Expected O(log n) O(log n) expected Randomised None built-in
AVL vs Red-Black β€” know the trade-off conceptually:
  • AVL trees are more strictly balanced (|h(L) βˆ’ h(R)| ≀ 1 at every node) which makes lookups slightly faster β€” at the cost of more rotations on insert and delete.
  • Red-Black trees are less strictly balanced but require fewer rotations, making insert and delete faster in practice.
  • Java chose Red-Black for TreeMap and TreeSet because real-world workloads involve more writes than reads in sorted collections.
07
Section Seven Β· Delete

The Delete Problem β€” Three Cases

Deletion is the hardest BST operation because removing a node with two children cannot eliminate it directly β€” the tree must be restructured to maintain the BST property, and the restructuring strategy is non-obvious until you understand why the inorder successor is the correct replacement. The three cases β€” deleting a leaf, deleting a node with one child, and deleting a node with two children β€” must be handled separately, and only the third case requires any structural reasoning. The inorder successor (the leftmost node in the right subtree) is always the correct replacement for a two-child deletion because it is the smallest value larger than the deleted node, maintaining the BST property for all remaining nodes in both subtrees.

Delete β€” three cases side by side
Case 1: Delete Leaf 3 1 delete 1 4 3 4 null return null Case 2: One Child 10 delete 10 9 9 return child (9) Case 3: Two Children 8 delete 8 3 10 9 successor 14 leftmost in right subtree 9 3 10 14 replace 8 with 9 then delete 9 from right subtree
Delete Implementation
Java
// BST Delete β€” handles all 3 cases O(h) TreeNode delete(TreeNode node, int val) { if (node == null) return null; // not found if (val < node.val) { node.left = delete(node.left, val); // go left } else if (val > node.val) { node.right = delete(node.right, val); // go right } else { // FOUND the node to delete β€” handle 3 cases: // Case 1: leaf node if (node.left == null && node.right == null) return null; // Case 2: one child β€” return the non-null child if (node.left == null) return node.right; if (node.right == null) return node.left; // Case 3: two children β€” find inorder successor TreeNode successor = findMin(node.right); // leftmost in right subtree node.val = successor.val; // copy successor's value node.right = delete(node.right, successor.val); // delete successor } return node; } // Helper: find leftmost node (minimum value) TreeNode findMin(TreeNode node) { while (node.left != null) node = node.left; return node; }
Why the inorder successor β€” and not the predecessor?:
  • Both the inorder successor (smallest value in the right subtree) and the inorder predecessor (largest value in the left subtree) are valid replacements for a two-child deletion β€” both maintain the BST property.
  • The convention of using the inorder successor is a convention; some implementations use the predecessor.
  • What matters is understanding why either choice works: the replacement must be the value closest to the deleted node in sorted order.
Common Mistake: Confusing "delete the inorder successor node" with "restructure the tree." After copying the successor's value into the deleted node's position, the successor must be deleted from the right subtree β€” and that deletion is itself a recursive delete call. The successor has at most one child (no left child, by definition of leftmost), so this recursive call always reduces to Case 1 or Case 2 β€” it never recurses into Case 3 again.
08
Section Eight Β· Implementation

Build It Once

Build this from scratch once β€” it makes the mechanics concrete. In any real project or interview, reach for the built-in (Section 09).

Java β€” BST core mechanics
// BST operations follow the search path β€” go left if smaller, // right if larger. All three operations here are O(h): O(log n) // balanced, O(n) degenerate. // Search β€” iterative (preferred over recursive) O(h) TreeNode search(TreeNode node, int target) { while (node != null && node.val != target) node = (target < node.val) ? node.left : node.right; return node; // null if not found, node if found } // Insert β€” recursive with return-node pattern O(h) TreeNode insert(TreeNode node, int val) { if (node == null) return new TreeNode(val); if (val < node.val) node.left = insert(node.left, val); else if (val > node.val) node.right = insert(node.right, val); return node; // return-node pattern rewires parent pointers } // Validate BST β€” min/max bounds propagation O(n) boolean isValidBST(TreeNode node, long min, long max) { if (node == null) return true; if (node.val <= min || node.val >= max) return false; return isValidBST(node.left, min, node.val) // tighten max && isValidBST(node.right, node.val, max); // tighten min } // Initial call: isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE)
09
Section Nine Β· Java Reference

Use It In Java

IN JAVA β€” TreeMap and TreeSet are BSTs
TreeMap<K,V> β€” BST operations on a sorted map
TreeMap<Integer, String> map = new TreeMap<>(); // Standard operations β€” O(log n) map.put(8, "eight"); map.put(3, "three"); map.put(10, "ten"); // BST-specific navigation β€” the BST advantage in Java map.floorKey(7); // β†’ 3 (largest key ≀ 7) map.ceilingKey(7); // β†’ 8 (smallest key β‰₯ 7) map.higherKey(8); // β†’ 10 (strictly greater than 8) map.lowerKey(8); // β†’ 3 (strictly less than 8) map.firstKey(); // β†’ 3 (minimum β€” leftmost node) map.lastKey(); // β†’ 10 (maximum β€” rightmost node) // Sorted iteration β€” inorder traversal for (Map.Entry<Integer,String> e : map.entrySet()) { // visits entries in ascending key order β€” BST inorder } // Range view β€” O(log n) to obtain, O(k) to iterate map.subMap(3, true, 10, false); // keys in [3, 10)
TreeSet<E> β€” BST operations on a sorted set
TreeSet<Integer> set = new TreeSet<>(); set.add(8); set.add(3); set.add(10); set.add(1); set.floor(7); // β†’ 3 (largest element ≀ 7) set.ceiling(7); // β†’ 8 (smallest element β‰₯ 7) set.higher(8); // β†’ 10 (strictly greater) set.lower(8); // β†’ 3 (strictly less) set.first(); // β†’ 1 (minimum) set.last(); // β†’ 10 (maximum) // Sorted iteration for (int val : set) { /* ascending order */ }
When to use what
Need Use BST Operation Under the Hood
Sorted key→value storage TreeMap<K,V> Red-Black BST with O(log n) nav
Sorted unique values TreeSet<E> Red-Black BST (same as TreeMap's keyset)
Floor/ceiling/range queries TreeMap or TreeSet BST left/right traversal
Custom BST / structural problems Custom TreeNode Build from scratch for interviews
Priority ordering (not sorted) PriorityQueue Binary min-heap β€” not a BST
⚠ Gotcha: TreeMap and TreeSet maintain sorted order and provide BST-like navigation, but they expose no node-level API β€” you cannot access parent pointers, left/right children, or perform rotations. For interview problems requiring structural tree manipulation (LCA, serialize/deserialize, validate BST), always build a custom TreeNode from scratch.
⚠ Gotcha: Use long (not int) as bounds when validating a BST in Java β€” node values can be Integer.MIN_VALUE or Integer.MAX_VALUE, and passing int min/max bounds causes overflow. Initialize: isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE).
10
Section Ten Β· Practice

Problems To Solve

BST problems split into two categories: structural problems that require understanding the BST property itself (validate BST, insert, delete, lowest common ancestor), where the ordering invariant drives every decision, and sorted-output problems that exploit inorder traversal (kth smallest, convert sorted array to BST, recover BST). Before writing code on any BST problem, confirm whether the BST property actually helps β€” some problems on BST nodes are solved exactly like general binary tree problems (height, depth, symmetry), while others are O(log n) precisely because the ordering allows elimination of an entire subtree at every comparison.

Difficulty Pattern Problem Key Insight
Medium dfs Validate Binary Search Tree β€” LC 98 Pass long min and max bounds top-down β€” not int, to avoid overflow with Integer.MIN_VALUE/MAX_VALUE inputs. A node is invalid if node.val ≀ min or node.val β‰₯ max.
Easy dfs Search in a Binary Search Tree β€” LC 700 Prefer the iterative version β€” while node != null, go left if target < node.val, go right if larger, return node if equal. No recursion overhead, no call stack.
Medium dfs Insert into a Binary Search Tree β€” LC 701 Recurse left or right to the null position, return a new TreeNode(val). The return-node pattern updates parent pointers without a separate parent reference.
Medium dfs Delete Node in a BST β€” LC 450 Three cases in order: (1) leaf β†’ return null, (2) one child β†’ return that child, (3) two children β†’ copy inorder successor value then recursively delete successor from right subtree.
Medium dfs Kth Smallest Element in a BST β€” LC 230 Iterative inorder with an early exit β€” decrement a counter on each visit, return when counter reaches zero. Cost is O(h + k), not O(n). Recursive version needs a class-level or array counter to enable early exit.
Medium dfs Lowest Common Ancestor of a BST β€” LC 235 Exploit BST ordering: if both p and q are less than node, go left; if both greater, go right; otherwise node is the LCA. O(log n) on balanced BST β€” faster than general binary tree LCA.
Interview Pattern:
  • For BST problems, always ask first: does this problem need to exploit the BST ordering property, or is it a binary tree problem that happens to be on a BST?
  • If the ordering matters (search, insert, delete, LCA, floor/ceiling), the BST property drives every decision.
  • If the ordering does not matter (height, balance check, level-order), solve it exactly as you would a general binary tree.
  • Conflating the two approaches leads to O(n) solutions for problems that should be O(log n).

β†’ See all Binary Search Tree problems with full solutions