Chillymosh's Blog

Back

Binary Search Trees (BSTs) are one of the most important and widely used types of binary trees. By maintaining a sorted structure, BSTs provide efficient searching, insertion, and deletion operations at the heart of sets, maps, databases, and more.

In this post, we’ll cover:

  • What makes a tree a BST
  • Search, insertion, and deletion algorithms
  • Average vs worst-case performance
  • Common pitfalls and balancing needs
  • Practical code examples

What is a Binary Search Tree?#

A Binary Search Tree (also called an ordered or sorted binary tree) is a tree-based data structure where each node follows a strict organising rule: its value is greater than everything in its left subtree and less than everything in its right subtree. This ordering keeps the tree’s data sorted as you insert or remove nodes, which enables fast searching, insertion, and deletion. This is often much more efficient than a simple list.

Imagine a perfectly organised filing system where every bit of data knows exactly where it belongs. The performance of searching, inserting, or deleting all hinges on the tree’s height. Shorter, well-balanced trees deliver the best performance.

  • Left side: All values smaller than the current node
  • Right side: All values larger than the current node
  • Universal rule: This pattern holds true at every single node in the tree

This simple principle makes sure your data stays sorted, which makes searching incredibly fast.

1571949211820

How to efficiently find a value in a BST:

  1. Start at the root.
  2. If the value equals the root, you’re done.
  3. If less, search the left subtree; if greater, search the right.
  4. Repeat until found or you hit a leaf. If the latter then you would return None.
1571949211820

The time complexity for searching a BST for a value is O(h) where h is the height of the tree. If the tree is unbalanced then the height of the tree often becomes larger than it needs to be, which will cause searches to take longer.

Recursive#

def search(node, target):
    if node is None:
        return None
    if node.value == target:
        return node
    if target < node.value:
        return search(node.left, target)
    else:
        return search(node.right, target)
python

Iterative#

def search(node, target):
    while node:
        if node.value == target:
            return node
        elif target < node.value:
            node = node.left
        else:
            node = node.right
    return None
python

Insert#

How to insert a new value while maintaining the BST property:

  1. Start at the root.
  2. If less, go left; if greater, go right.
  3. Repeat until you find a None/null spot.
  4. Insert the new node there.
15719492118205

Recursive#

def insert(node, data):
    if node is None:
        return Node(data)
    else:
        if data < node.val:
            node.left = insert(node.left, data)
        elif data > node.val:
            node.right = insert(node.right, data)
    return node
python

Iterative#

Iterative BST insertion is possible but less common in practice. Recursive methods are clearer for most cases.

Iteration is used when the application is performance critical or the Binary Tree is deep enough it could cause a stack overflow.

def insert(node, data):
    if root is None:
        return Node(data)
    
    current = root
    while True:
        if data < current.val:
            if current.left is None:
                current.left = Node(data)
                break
            else:
                current = current.left
        elif data > current.val:
            if current.right is None:
                current.right = Node(data)
                break
            else:
                current = current.right
        else:
            break 
    
    return root
python

Delete#

Deleting a value from a BST is more involved:

  • If the node is a leaf: remove it.
  • If it has one child: replace it with its child.
  • If it has two children then you have two options:
    • Replace it with its in-order successor (smallest node in right subtree)
    • Replace it with its in-order predecessor (largest node in left subtree)
    • After either of the above, remove the target node.
15719492118205

Deleting the node with value of 5

Recursive#

These examples cover every deletion case in a BST and use small, purpose built helpers for clarity. In real world code you can simplify further.

e.g. merge find_successor and find_predecessor into one function, or even inline that logic directly into your delete function if you don’t need the separate helpers.

def find_successor(node):
    current = node.right
    while current.left is not None:
        current = current.left
    return current

def find_predecessor(node):
    current = node.left
    while current.right is not None:
        current = current.right
    return current

def delete(root, target, use_successor=True):
    if root is None:
        return None

    if target < root.val:
        root.left = delete(root.left, target, use_successor)
    elif target > root.val:
        root.right = delete(root.right, target, use_successor)
    elif root.left is None and root.right is None:
        return None
    elif root.left is None:
        return root.right
    elif root.right is None:
        return root.left
    else:
        if use_successor:
            replacement = find_successor(root)
            root.val = replacement.val
            root.right = delete(root.right, replacement.val, use_successor)
        else:
            replacement = find_predecessor(root)
            root.val = replacement.val
            root.left = delete(root.left, replacement.val, use_successor)

    return root
python

Iterative#

Note that the helper functions now also return the current node and the parent node as a tuple.

def find_successor(node):
    current = node.right
    parent = node
    while current.left is not None:
        parent = current
        current = current.left
    return current, parent

def find_predecessor(node):
    current = node.left
    parent = node
    while current.right is not None:
        parent = current
        current = current.right
    return current, parent

def delete(root, key, use_successor=True):
    if root is None:
        return root

    parent = None
    current = root

    while current is not None and current.val != key:
        parent = current
        current = current.left if key < current.val else current.right

    if current is None:
        return root

    if current.left is None and current.right is None:
        if parent is None:
            return None  
        elif parent.left == current:
            parent.left = None
        else:
            parent.right = None

    elif current.left is None:
        if parent is None:
            return current.right
        elif parent.left == current:
            parent.left = current.right
        else:
            parent.right = current.right

    elif current.right is None:
        if parent is None:
            return current.left 
        elif parent.left == current:
            parent.left = current.left
        else:
            parent.right = current.left

    else:
        if use_successor:
            replacement, replacement_parent = find_successor(current)
        else:
            replacement, replacement_parent = find_predecessor(current)

        current.val = replacement.val

        if use_successor:
            if replacement_parent == current:
                replacement_parent.right = replacement.right
            else:
                replacement_parent.left = replacement.right
        elif replacement_parent == current:
            replacement_parent.left = replacement.left
        else:
            replacement_parent.right = replacement.left

    return root
python

Time Complexity#

For any BST operation (search, insert, delete), the time complexity is O(h) where h is the tree’s height.

Tree StructureHeight (h)Time ComplexityExample
BalancedO(log n)O(log n)Perfect binary tree
UnbalancedO(n)O(n)Linked list structure / skewed

Balanced Trees (Best Case)#

  • Height approximately log₂ n
  • Performance: O(log n) for all operations
  • Example: With 1,000 nodes, height ≈ 10 levels

Skewed Trees (Worst Case)#

  • Height equals n (essentially a linked list)
  • Performance: O(n) for all operations
  • Example: Inserting sorted data: 1→2→3→4→5

Space Complexity#

ImplementationSpace ComplexityNotes
RecursiveO(h)Call stack grows with height
IterativeO(1)Constant extra space

Why Balancing Matters#

Without balancing:

  • Sorted input creates O(n) height
  • Performance degrades to linear search
  • Defeats the purpose of using a BST

With self-balancing (AVL, Red-Black):

  • Automatic rotations maintain O(log n) height
  • Guaranteed logarithmic performance
  • Slight overhead for rebalancing operations

Common Pitfalls#

  • Inserting sorted data leads to an unbalanced, degenerate tree (poor performance).
  • BST property violations can occur with incorrect insert/delete logic.
  • Duplicate values: handle as per requirements (left/right, or disallow).
  • Memory leaks in C/C++ when forgetting to free deleted nodes.
  • Parent pointer management errors during deletion (especially with two children).
  • Edge case bugs: deleting root node, single-node trees, or non-existent keys.
  • Mixing recursive/iterative approaches inconsistently in the same codebase.
  • Assuming balanced performance without using self-balancing variants.

Summary#

  • Best Practice: Use self-balancing BSTs (AVL, Red-Black) for guaranteed O(log n) performance.

  • Height Determines Performance: All BST operations are O(h); balanced trees ensure optimal performance.

  • Iterative vs. Recursive:

    • Recursive approaches are simpler and easier to understand, suitable when height (h) is guaranteed to be O(log n).
    • Iterative approaches can optimize space complexity O(1), beneficial when height could be large (potentially O(n)) or in memory-constrained environments.

What’s Next#

In Post 4: Self-Balancing Trees - AVL & Red-Black, we’ll build on our BST foundations to see:

  • Rotations (left & right) step by step
  • Invariants that distinguish AVL vs. Red-Black trees
  • When to pick which structure for your use case
Binary Search Trees: Fast Lookups and Dynamic Data
https://chillymosh.com/blog/binary-search-trees
Author Chillymosh
Published at 3 August 2025