Insert into a BST is the classic warm-up for binary search trees. It teaches the move that powers every BST operation: at each node, one comparison tells you whether to go left or right, so you reach the right spot in the height of the tree rather than scanning everything.
Problem. Given the root of a binary search tree and a value val to insert, add a new node with
that value and return the root of the tree. The input value is guaranteed not to already be in the tree,
and you may return any valid BST as long as the ordering invariant holds.
Example: insert 6 into the BST [5, 3, 8, 2, 4, 7]. The new node attaches as the right child of 7,
giving in-order 2, 3, 4, 5, 6, 7, 8.
The slow way first
You could collect every value, append the new one, and rebuild a balanced tree from scratch. That throws away the structure you already have and costs O(n) work plus a full rebuild for a single insert. It also ignores the one thing a BST gives you for free: a built-in compass.
The question to ask: at each node, do I even need to look at both subtrees? No. The BST invariant means a smaller value can only ever live on the left and a larger value only on the right. So one comparison rules out an entire half of the remaining tree.
The idea: walk down comparing, attach at the null
Start at the root. Compare val with the current node. If val is smaller, move into the left child; if larger, move into the right child. Keep walking until the child you want to step into is None — you have fallen off the bottom of the tree. That empty slot is exactly where the new value belongs, so create a leaf there.
The key insight: a new value always lands as a leaf. Walking down never disturbs existing nodes, so the ordering everywhere above the insertion point is automatically preserved.
Walk through it
Step through the animation. The cur pointer walks down the tree. Inserting 6: 6 > 5 so we go right to 8; 6 < 8 so we go left to 7; 6 > 7 so we go right — but 7 has no right child. That None slot is where 6 attaches as a fresh leaf, and the BST order still reads cleanly in-order.
Pseudocode
insert(node, val):
if node is None: # fell off the tree
return a new leaf holding val
if val < node.value:
node.left = insert(node.left, val)
else:
node.right = insert(node.right, val)
return node # tree above is unchangedThe Python solution
def insert_into_bst(root, val):
if root is None:
return TreeNode(val)
if val < root.val:
root.left = insert_into_bst(root.left, val)
else:
root.right = insert_into_bst(root.right, val)
return root- The base case
root is Nonemeans we walked off the tree — return a brand-new leaf node, which the caller wires into the empty slot. if val < root.valis the single comparison that picks a direction — left for smaller.root.left = insert_into_bst(root.left, val)recurses left and re-attaches the (possibly new) subtree. When the recursion returns a fresh leaf, this is the line that links it in.- The
elsebranch does the mirror move to the right for any value not smaller. return roothands back the same node we entered with, so every level above the insertion point is left untouched.
Complexity
| Case | Time | Notes |
|---|---|---|
| Balanced tree | O(log n) (fast) | one comparison per level, height is log n |
| Degenerate (sorted) tree | O(n) (moderate) | the tree is a straight line |
O(h) (moderate)The work is proportional to the height of the tree: one comparison per level as we walk down. In a balanced tree that is O(log n); if the tree has degenerated into a chain, it is O(n). The recursion uses O(h) stack space.
When this pattern shows up
Any BST operation — search, insert, delete, floor/ceiling, range queries — is the same walk: compare at the node, then commit to exactly one child. If you find yourself looking at both subtrees of a BST, you are probably ignoring the invariant that makes it fast.
The cost is the tree height, not the node count. A BST built by inserting already-sorted values degenerates into a linked list, pushing inserts to O(n). Self-balancing trees (AVL, red-black) exist precisely to keep the height at O(log n).
Practice
Insert 6 into the BST with root 5 (right child 8, whose left child is 7). Which node does 6 become a child of, and on which side?
1. Where does a newly inserted value always end up in this algorithm?
2. At each node, how many subtrees do we need to examine?
3. What determines the time complexity of the insert?
4. Why might inserting already-sorted values be slow?