Binary Search Tree (BST)

A Binary Search Tree is a regular binary tree with exactly one extra rule applied at every single node, and that one rule is what turns "a tree" into "a structure you can search in O(log n)." This lesson codes insert, search, and delete against that rule, then shows exactly why the O(log n) claim is an average-case claim, not a guarantee.

The BST Invariant

For every node in the tree:

  • Every value in its left subtree is less than the node's value.
  • Every value in its right subtree is greater than the node's value.
This holds recursively at every node, not just the root. The left child of the root must itself have a smaller left subtree and a larger (but still smaller than the root) right subtree, and so on all the way down. This is exactly why inorder traversal of a BST visits nodes in sorted order — it's the direct consequence of this invariant.
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }
}

Insert

To insert a value, walk from the root: go left if the new value is smaller, right if it's larger, and when you fall off the tree (hit null), that's where the new node goes.

public static TreeNode insert(TreeNode root, int value) {
    if (root == null) {
        return new TreeNode(value);      // base case: found the empty spot
    }
    if (value < root.val) {
        root.left = insert(root.left, value);
    } else if (value > root.val) {
        root.right = insert(root.right, value);
    }
    // value == root.val: duplicate, no-op (BSTs commonly disallow duplicates)
    return root;
}

Time complexity: O(h) — one comparison per level, where h is the tree's height (O(log n) average, O(n) worst case — see below). Space complexity: O(h) — recursion stack depth equals the path walked.

Search

public static boolean search(TreeNode root, int target) {
    if (root == null) {
        return false;                    // fell off the tree without finding it
    }
    if (target == root.val) {
        return true;
    }
    return target < root.val
        ? search(root.left, target)
        : search(root.right, target);
}

Time complexity: O(h) — at each step exactly one comparison eliminates the entire opposite subtree from consideration, same height bound as insert. Space complexity: O(h) — recursion stack.

Why this beats a plain sorted array for insertion: binary search on a sorted array finds a value in O(log n) too, but inserting into a sorted array is O(n) because you have to shift every element after the insertion point. A BST gets O(log n) search-like behavior for insert too, because there's no shifting — you just attach a new node.

Delete

Delete is the trickiest of the three because removing a node with two children would leave a gap — you can't just null it out. The standard approach: find the node's inorder successor (the smallest value in its right subtree — walk left-child pointers as far as possible from root.right), copy that value up into the node being "deleted," and then delete the successor from the right subtree instead (which is guaranteed to be the easy case, since the successor has no left child by definition).

public static TreeNode delete(TreeNode root, int value) {
    if (root == null) {
        return null;                     // value not found, nothing to do
    }

    if (value < root.val) {
        root.left = delete(root.left, value);
    } else if (value > root.val) {
        root.right = delete(root.right, value);
    } else {
        // found the node to delete
        if (root.left == null) {
            return root.right;             // 0 or 1 child (right): promote right child
        }
        if (root.right == null) {
            return root.left;              // 1 child (left): promote left child
        }

        // 2 children: find inorder successor = leftmost node of right subtree
        TreeNode successor = root.right;
        while (successor.left != null) {
            successor = successor.left;
        }
        root.val = successor.val;                       // copy successor's value up
        root.right = delete(root.right, successor.val);  // remove the successor's original node
    }
    return root;
}

Time complexity: O(h) — locating the node is O(h), and locating the successor is bounded by another O(h) walk down the right subtree, so total is still O(h). Space complexity: O(h) — recursion stack.

Average Case O(log n) vs Worst Case O(n)

Every operation above costs O(h), where h is the tree's height. The O(log n) claim you hear about BSTs is really a claim about h — and h is only O(log n) when the tree is reasonably balanced (roughly the same number of nodes on the left and right of every subtree). Nothing in the insert code above enforces that. Insert data in a bad order, and the tree degenerates.

// Inserting already-sorted data, in order, into an empty BST:
TreeNode root = null;
int[] sortedInput = {1, 2, 3, 4, 5, 6};
for (int value : sortedInput) {
    root = insert(root, value);
}
Every new value is larger than everything already in the tree, so every insert goes right, and only right. The "tree" becomes a straight line: 1 \ 2 \ 3 \ 4 \ 5 \ 6 This is a "degenerate" or "skewed" tree — height h = n = 6, not log2(6) ~ 2.6. Searching for 6 now means walking through 1, 2, 3, 4, 5 first: a linked list wearing a TreeNode costume.

Search, insert, and delete on this degenerate tree all cost O(n) — you've lost every advantage a BST was supposed to give you over a plain array, while paying extra memory for left/right pointers you never use.

OperationAverage case (balanced-ish tree)Worst case (skewed tree)
SearchO(log n)O(n)
InsertO(log n)O(n)
DeleteO(log n)O(n)
The takeaway an interviewer wants to hear: "BST operations are O(log n) on average, but O(n) worst case if the tree becomes skewed — for example by inserting sorted or near-sorted data." Saying "BSTs are O(log n)" with no caveat is the kind of gloss that gets follow-up questions.
Up next: Balanced Trees (Intro) exists to solve exactly this problem — structures like AVL trees and Red-Black trees automatically re-balance after every insert/delete so height stays O(log n) no matter what order the data arrives in. First, though, Heaps & Priority Queue covers a different tree-shaped structure built for a different job: always knowing the smallest (or largest) element instantly.