title: Insert into a Binary Search Tree
slug: insert-into-a-binary-search-tree
difficulty: easy
leetcode_id: 701
leetcode_url: https://leetcode.com/problems/insert-into-a-binary-search-tree/
categories:
  - trees
patterns:
  - tree-traversal

description: |
  You are given the `root` node of a binary search tree (BST) and a `val` to insert into the tree. Return *the root node of the BST after the insertion*. It is **guaranteed** that the new value does not exist in the original BST.

  **Notice** that there may exist multiple valid ways for the insertion, as long as the tree remains a BST after insertion. You can return **any of them**.

constraints: |
  - `0 <= Number of nodes <= 10^4`
  - `-10^8 <= Node.val <= 10^8`
  - All values `Node.val` are **unique**
  - `-10^8 <= val <= 10^8`
  - It's **guaranteed** that `val` does not exist in the original BST

examples:
  - input: "root = [4,2,7,1,3], val = 5"
    output: "[4,2,7,1,3,5]"
    explanation: "Insert 5 as the left child of 7, since 5 < 7 and 7 has no left child. Another valid answer would insert 5 elsewhere while maintaining BST properties."
  - input: "root = [40,20,60,10,30,50,70], val = 25"
    output: "[40,20,60,10,30,50,70,null,null,25]"
    explanation: "Navigate right from 20 (since 25 > 20), then left from 30 (since 25 < 30). Insert 25 as the left child of 30."
  - input: "root = [], val = 5"
    output: "[5]"
    explanation: "When the tree is empty, the new value becomes the root."

explanation:
  intuition: |
    Think of a BST as a decision tree for binary search. At each node, you ask: "Is my value smaller or larger than this node?" The answer tells you which direction to go — left for smaller, right for larger.

    The key insight is that **every value has exactly one "correct" leaf position** where it can be inserted without restructuring the tree. You simply follow the BST property until you hit an empty spot (`None`), and that's where the new node belongs.

    Imagine you're looking up a word in a dictionary. You flip to the middle, decide if your word comes before or after, and keep narrowing down. When you reach the exact spot where your word *would* be if it existed, that's where you insert it.

    This means insertion is essentially a **search that ends at a null pointer**, and we replace that null with our new node.

  approach: |
    We solve this using a **Recursive BST Traversal**:

    **Step 1: Handle the base case**

    - If `root` is `None`, we've found the insertion point
    - Create and return a new `TreeNode` with the given value

    &nbsp;

    **Step 2: Decide which subtree to explore**

    - If `val < root.val`, the new node belongs in the **left subtree**
    - If `val > root.val`, the new node belongs in the **right subtree**

    &nbsp;

    **Step 3: Recursively insert and connect**

    - Make a recursive call on the appropriate child
    - Assign the result back to `root.left` or `root.right`
    - This automatically handles the case where the child was `None`

    &nbsp;

    **Step 4: Return the root**

    - Return the (unchanged) root to maintain the tree structure
    - The recursive assignment ensures the new node gets properly linked

    &nbsp;

    The recursion naturally terminates when we reach a `None` child, which becomes the insertion point. By assigning the recursive result back to the parent's child pointer, we elegantly connect the new node.

  common_pitfalls:
    - title: Forgetting to Handle the Empty Tree
      description: |
        When `root` is `None` (empty tree), you must return a new node as the root. Some solutions only handle the case of inserting into existing trees, causing a crash or returning `None` for empty input.

        Always check for `root is None` as your base case and return the new node.
      wrong_approach: "Only handling non-empty trees"
      correct_approach: "Check if root is None and return new TreeNode(val)"

    - title: Not Connecting the New Node
      description: |
        A common mistake is to traverse to the correct position but forget to actually link the new node to its parent. Simply creating a new node isn't enough — you must assign it to the parent's `left` or `right` pointer.

        The recursive approach handles this elegantly by assigning `root.left = insertIntoBST(root.left, val)`.
      wrong_approach: "Creating node without linking to parent"
      correct_approach: "Assign recursive result back to parent's child pointer"

    - title: Modifying Existing Node Values
      description: |
        BST insertion adds a **new node**, not modifying an existing one. Don't try to swap values or restructure the tree — simply find the correct empty spot and insert there.

        The problem guarantees the value doesn't exist, so you'll always reach a `None` position.
      wrong_approach: "Changing existing node values"
      correct_approach: "Always insert as a new leaf node"

  key_takeaways:
    - "**BST property drives insertion**: Left for smaller, right for larger — no complex logic needed"
    - "**Recursion simplifies tree operations**: The base case handles insertion, recursive calls handle navigation"
    - "**Insertion is search + create**: Follow the search path until hitting `None`, then insert"
    - "**Foundation for BST operations**: This pattern extends to deletion, search, and validation problems"

  time_complexity: "O(h) where h is the height of the tree. In a balanced BST, h = log(n), giving O(log n). In the worst case (skewed tree), h = n, giving O(n)."
  space_complexity: "O(h) for the recursion stack. In a balanced tree this is O(log n), worst case O(n) for a skewed tree. The iterative approach achieves O(1) space."

solutions:
  - approach_name: Recursive
    is_optimal: true
    code: |
      class TreeNode:
          def __init__(self, val=0, left=None, right=None):
              self.val = val
              self.left = left
              self.right = right

      def insert_into_bst(root: TreeNode | None, val: int) -> TreeNode:
          # Base case: found the insertion point
          if root is None:
              return TreeNode(val)

          # Decide which subtree to insert into
          if val < root.val:
              # Value belongs in left subtree
              root.left = insert_into_bst(root.left, val)
          else:
              # Value belongs in right subtree
              root.right = insert_into_bst(root.right, val)

          # Return root to maintain tree structure
          return root
    explanation: |
      **Time Complexity:** O(h) — We traverse from root to a leaf, where h is the tree height.

      **Space Complexity:** O(h) — Recursion stack depth equals the path length.

      The recursive approach elegantly handles both navigation and connection. When we hit `None`, we return the new node, and the parent's assignment (`root.left = ...`) automatically links it.

  - approach_name: Iterative
    is_optimal: true
    code: |
      class TreeNode:
          def __init__(self, val=0, left=None, right=None):
              self.val = val
              self.left = left
              self.right = right

      def insert_into_bst(root: TreeNode | None, val: int) -> TreeNode:
          # Handle empty tree
          if root is None:
              return TreeNode(val)

          # Find the correct position
          current = root
          while True:
              if val < current.val:
                  # Go left
                  if current.left is None:
                      # Found insertion point
                      current.left = TreeNode(val)
                      break
                  current = current.left
              else:
                  # Go right
                  if current.right is None:
                      # Found insertion point
                      current.right = TreeNode(val)
                      break
                  current = current.right

          return root
    explanation: |
      **Time Complexity:** O(h) — Same traversal as recursive approach.

      **Space Complexity:** O(1) — No recursion stack, only a single pointer.

      The iterative approach uses a while loop to find the insertion point, then directly links the new node. This avoids recursion overhead and is slightly more efficient in practice, though both have the same time complexity.
