Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BST Insert and Remove

A BST is only useful if you can add and remove values while keeping it sorted. Insert is straightforward — follow the search path until you find an empty slot. Remove is trickier, because pulling a node out must not break the left < node < right property for every other node.

Insert: walk until you find the gap

Inserting a value is like searching for it, except instead of returning “not found” when you fall off the edge, you create a new node there.

Before inserting 5

flowchart TD
    A["8"] --> B["3"]
    A --> C["10"]
    B --> D["1"]
    B --> E["6"]
    E --> F["4"]
    E --> G["7"]

After inserting 5

The path: 5 < 8 → go left. 5 > 3 → go right. 5 < 6 → go left. 5 > 4 → go right. Empty slot — place 5 here.

flowchart TD
    A["8"] --> B["3"]
    A --> C["10"]
    B --> D["1"]
    B --> E["6"]
    E --> F["4"]
    E --> G["7"]
    F -->|"right"| NEW["5  ← new node"]
class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None


def insert(node, value):
    """
    Returns the root of the (possibly new) subtree.
    If node is None we've found the empty slot — create the node here.
    """
    if node is None:
        return TreeNode(value)
    if value < node.value:
        node.left = insert(node.left, value)
    elif value > node.value:
        node.right = insert(node.right, value)
    # value == node.value: duplicate — do nothing (BSTs store unique values)
    return node


def inorder(node):
    """In-order traversal of a BST returns values in sorted order."""
    if node is None:
        return []
    return inorder(node.left) + [node.value] + inorder(node.right)


# Build a BST by inserting values one at a time
root = None
for val in [8, 3, 10, 1, 6, 4, 7]:
    root = insert(root, val)

print("Before inserting 5:", inorder(root))

root = insert(root, 5)
print("After inserting 5: ", inorder(root))  # 5 appears in sorted position

Remove: the three cases

Removing a node is more involved because the node might have children that need to stay in the tree. There are exactly three situations:

Case 1 — The node is a leaf (no children)

Just delete it. Nothing else needs to move.

flowchart TD
    A["8"] --> B["3"]
    A --> C["10"]
    B --> D["1  ← remove this"]
    B --> E["6"]

After removing 1, node 3 simply has no left child.

Case 2 — The node has one child

Replace the node with its only child. The subtree slides up one level.

flowchart TD
    A["8"] --> B["3"]
    A --> C["10  ← remove this (has one child: 14)"]
    C --> D["14"]

After removing 10, node 8’s right child becomes 14.

Case 3 — The node has two children

This is the tricky case. We cannot simply delete the node because it has two subtrees that need a parent. The solution: replace the node’s value with its in-order successor (the smallest value in its right subtree), then delete that successor from the right subtree.

Why the in-order successor? It is the smallest value that is still larger than every value in the left subtree — so it is the perfect replacement to maintain the BST property.

flowchart TD
    A["8"] --> B["3  ← remove this"]
    A --> C["10"]
    B --> D["1"]
    B --> E["6"]
    E --> F["4"]
    E --> G["7"]

The in-order successor of 3 is 4 (smallest value in 3’s right subtree). Replace 3 with 4, then remove 4 from its original location:

flowchart TD
    A["8"] --> B["4  ← replaced 3 with its successor"]
    A --> C["10"]
    B --> D["1"]
    B --> E["6"]
    E --> EMPTY["(4 removed from here)"]
    E --> G["7"]

Full implementation

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None


def insert(node, value):
    if node is None:
        return TreeNode(value)
    if value < node.value:
        node.left = insert(node.left, value)
    elif value > node.value:
        node.right = insert(node.right, value)
    return node


def find_min(node):
    """The in-order successor is the leftmost node in a subtree."""
    while node.left is not None:
        node = node.left
    return node


def remove(node, value):
    """
    Returns the root of the subtree after removing the value.
    The BST property is preserved for every remaining node.
    """
    if node is None:
        return None  # value not in tree — nothing to do

    if value < node.value:
        node.left = remove(node.left, value)    # it's somewhere in the left subtree
    elif value > node.value:
        node.right = remove(node.right, value)  # it's somewhere in the right subtree
    else:
        # We found the node to delete.
        # Case 1: leaf node
        if node.left is None and node.right is None:
            return None
        # Case 2: one child — return the child, skipping this node
        if node.left is None:
            return node.right
        if node.right is None:
            return node.left
        # Case 3: two children — replace with in-order successor
        successor = find_min(node.right)
        node.value = successor.value           # overwrite this node's value
        node.right = remove(node.right, successor.value)  # delete the successor

    return node


def inorder(node):
    if node is None:
        return []
    return inorder(node.left) + [node.value] + inorder(node.right)


# Build the tree
root = None
for val in [8, 3, 10, 1, 6, 4, 7, 14]:
    root = insert(root, val)

print("Initial tree (sorted):", inorder(root))

# Case 1: remove a leaf
root = remove(root, 1)
print("After removing 1 (leaf):           ", inorder(root))

# Case 2: remove a node with one child
root = remove(root, 10)
print("After removing 10 (one child):     ", inorder(root))

# Case 3: remove a node with two children
root = remove(root, 3)
print("After removing 3 (two children):   ", inorder(root))

The tree stays sorted after every operation

Notice that in-order traversal always produces a sorted list, even after multiple insertions and removals. This is the BST invariant: every operation either preserves or explicitly restores the left < node < right property at every node.

Real-world: dynamic sorted sets and ordered maps

Python’s sortedcontainers.SortedList uses a variant of this technique to give you a list that stays sorted automatically as you add and remove items — useful when you need both fast lookup and sorted iteration.

Java’s TreeMap and TreeSet are backed by a self-balancing BST (Red-Black tree). Every put() is an insert, every remove() is our three-case deletion, and firstKey() / lastKey() exploit the fact that in-order traversal is sorted.

Database ordered indexes use B-Trees (a generalisation of BSTs with many children per node), and the same three-case deletion logic applies to keeping them consistent.