Binary Search Trees in Python: Insert, Search, and Three Traversal Orders

A binary search tree keeps every left subtree’s values smaller than its node and every right subtree’s values larger (or equal), which is what makes search and sorted traversal both possible on the same structure. This post builds a BST from scratch, inserts 8 values, then runs inorder/preorder traversals and several search calls against it.

The code

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


class BST:
    def __init__(self):
        self.root = None

    def insert(self, value):
        # Smaller values go left, larger (or equal) go right, recursively,
        # until an empty spot is found.
        if self.root is None:
            self.root = TreeNode(value)
            return
        self._insert(self.root, value)

    def _insert(self, node, value):
        if value < node.value:
            if node.left is None:
                node.left = TreeNode(value)
            else:
                self._insert(node.left, value)
        else:
            if node.right is None:
                node.right = TreeNode(value)
            else:
                self._insert(node.right, value)

    def search(self, value):
        return self._search(self.root, value)

    def _search(self, node, value):
        if node is None:
            return False
        if node.value == value:
            return True
        if value < node.value:
            return self._search(node.left, value)
        return self._search(node.right, value)

    def inorder(self):
        # left, node, right -> visits values in sorted order for a BST
        result = []
        self._inorder(self.root, result)
        return result

    def _inorder(self, node, result):
        if node is None:
            return
        self._inorder(node.left, result)
        result.append(node.value)
        self._inorder(node.right, result)

    def preorder(self):
        # node, left, right
        result = []
        self._preorder(self.root, result)
        return result

    def _preorder(self, node, result):
        if node is None:
            return
        result.append(node.value)
        self._preorder(node.left, result)
        self._preorder(node.right, result)

    def height(self):
        return self._height(self.root)

    def _height(self, node):
        if node is None:
            return 0
        return 1 + max(self._height(node.left), self._height(node.right))


values = [50, 30, 70, 20, 40, 60, 80, 10]
tree = BST()
for v in values:
    tree.insert(v)

print("inserted:", values)
print("inorder (should be sorted):", tree.inorder())
print("preorder (insertion-shaped):", tree.preorder())
print("height:", tree.height())

for target in [40, 45, 80, 5]:
    print(f"search({target}) ->", tree.search(target))

8 values are inserted one at a time into an empty BST. inorder visits left-subtree, node, right-subtree recursively; preorder visits node, left-subtree, right-subtree. search walks left or right depending on the comparison at each node.

Running it

Real output:

inserted: [50, 30, 70, 20, 40, 60, 80, 10]
inorder (should be sorted): [10, 20, 30, 40, 50, 60, 70, 80]
preorder (insertion-shaped): [50, 30, 20, 10, 40, 70, 60, 80]
height: 4
search(40) -> True
search(45) -> False
search(80) -> True
search(5) -> False

inorder returned [10, 20, 30, 40, 50, 60, 70, 80] — fully sorted, from an insertion order that wasn’t sorted at all. preorder returned [50, 30, 20, 10, 40, 70, 60, 80], starting with 50 (the root, inserted first) rather than the smallest value. height came out to 4 for these 8 values. Of the four searches, search(40) and search(80) (both inserted values) returned True; search(45) and search(5) (never inserted) returned False.

Takeaway

Same 8 values, same tree: inorder produced sorted output while preorder reproduced the tree’s insertion-driven shape, and search correctly separated the two present values (40, 80) from the two absent ones (45, 5).