title: Kth Smallest Element in a BST
slug: kth-smallest-element-in-a-bst
difficulty: medium
leetcode_id: 230
leetcode_url: https://leetcode.com/problems/kth-smallest-element-in-a-bst/
categories:
  - trees
  - recursion
patterns:
  - dfs
  - tree-traversal

description: |
  Given the `root` of a binary search tree, and an integer `k`, return the `k`<sup>th</sup> smallest value (**1-indexed**) of all the values of the nodes in the tree.

constraints: |
  - The number of nodes in the tree is `n`
  - `1 <= k <= n <= 10^4`
  - `0 <= Node.val <= 10^4`

examples:
  - input: "root = [3,1,4,null,2], k = 1"
    output: "1"
    explanation: "The inorder traversal of the tree is [1, 2, 3, 4]. The 1st smallest element is 1."
  - input: "root = [5,3,6,2,4,null,null,1], k = 3"
    output: "3"
    explanation: "The inorder traversal is [1, 2, 3, 4, 5, 6]. The 3rd smallest element is 3."

explanation:
  intuition: |
    The key insight comes from the fundamental property of a **Binary Search Tree (BST)**: for any node, all values in its left subtree are smaller, and all values in its right subtree are larger.

    What does this mean for traversal? If you visit nodes in **inorder** order (left → current → right), you get all values in **sorted ascending order**!

    Imagine walking through the tree: you go as far left as possible first (smallest values), then visit the current node, then explore the right subtree. This naturally produces a sorted sequence.

    So finding the k<sup>th</sup> smallest becomes simple: perform an inorder traversal and return the k<sup>th</sup> element you encounter. No need to collect all values first — you can count as you go and stop early once you've found it.

    Think of it like this: the BST is already "pre-sorted" by its structure. The inorder traversal simply reads this sorted order.

  approach: |
    We solve this using **Inorder Traversal with Early Termination**:

    **Step 1: Understand the traversal pattern**

    - Inorder traversal visits: left subtree → current node → right subtree
    - For a BST, this visits nodes in ascending sorted order
    - We count each node we visit until we reach the k<sup>th</sup> one

    &nbsp;

    **Step 2: Set up tracking variables**

    - `count`: Track how many nodes we've visited (starts at `0`)
    - `result`: Store the k<sup>th</sup> smallest value once found

    &nbsp;

    **Step 3: Perform inorder traversal**

    - Recursively traverse the left subtree
    - Increment `count` when visiting the current node
    - If `count == k`, we've found our answer — store it and stop
    - Otherwise, recursively traverse the right subtree

    &nbsp;

    **Step 4: Early termination**

    - Once we've found the k<sup>th</sup> element, there's no need to continue traversing
    - We can use a flag or check if result is set to stop recursion early

    &nbsp;

    This approach leverages the BST property to avoid sorting, achieving O(H + k) time where H is the tree height.

  common_pitfalls:
    - title: Collecting All Values Then Sorting
      description: |
        A naive approach collects all node values into a list, sorts it, and returns the k<sup>th</sup> element.

        This works but wastes both time and space. With n nodes, you'd use O(n) space for the list and O(n log n) time for sorting.

        Since BST's inorder traversal is already sorted, we can get O(H + k) time and O(H) space instead.
      wrong_approach: "Collect all values, sort, return kth"
      correct_approach: "Use inorder traversal property — already sorted"

    - title: Not Using Early Termination
      description: |
        Even with inorder traversal, visiting all n nodes is wasteful when k is small. If k = 1, why traverse the entire tree?

        Always check if you've found the k<sup>th</sup> element and stop early. This improves average case performance significantly, especially when k << n.
      wrong_approach: "Complete full inorder traversal"
      correct_approach: "Stop as soon as kth element is found"

    - title: Off-by-One Errors with 1-Indexed k
      description: |
        The problem states k is **1-indexed**, meaning k = 1 refers to the smallest element, not the second smallest.

        Be careful with your counter: if you start counting at 0, the k<sup>th</sup> element is found when `count == k`, not `count == k - 1`.

        Always clarify the indexing in your head before implementing.
      wrong_approach: "Returning element at index k-1 after 0-indexed counting"
      correct_approach: "Increment count first, then check if count equals k"

  key_takeaways:
    - "**BST inorder = sorted order**: This fundamental property is the key to many BST problems"
    - "**Early termination**: Stop traversing once you have the answer — don't process unnecessary nodes"
    - "**Iterative vs recursive**: Both work; iterative uses an explicit stack and can be easier to control for early termination"
    - "**Follow-up insight**: For frequent queries with modifications, augment nodes with subtree sizes for O(H) queries"

  time_complexity: "O(H + k). We descend H levels to reach the leftmost node, then visit k nodes. For a balanced tree, this is O(log n + k)."
  space_complexity: "O(H). The recursion stack or explicit stack holds at most H nodes, where H is the tree height — O(log n) for balanced, O(n) for skewed."

solutions:
  - approach_name: Inorder Traversal (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 kth_smallest(root: TreeNode | None, k: int) -> int:
          count = 0
          result = 0

          def inorder(node: TreeNode | None) -> bool:
              nonlocal count, result

              if not node:
                  return False

              # Traverse left subtree first (smaller values)
              if inorder(node.left):
                  return True  # Already found, stop early

              # Visit current node
              count += 1
              if count == k:
                  result = node.val
                  return True  # Found the kth smallest

              # Traverse right subtree (larger values)
              return inorder(node.right)

          inorder(root)
          return result
    explanation: |
      **Time Complexity:** O(H + k) — Descend to leftmost node (H steps), then visit k nodes.

      **Space Complexity:** O(H) — Recursion stack depth equals tree height.

      We perform inorder traversal, counting nodes as we visit them. The `True` return value signals that we've found the answer and should stop recursing. This early termination avoids unnecessary traversal when k is small.

  - approach_name: Inorder Traversal (Iterative with Stack)
    is_optimal: true
    code: |
      def kth_smallest(root: TreeNode | None, k: int) -> int:
          stack = []
          current = root
          count = 0

          while stack or current:
              # Go as far left as possible
              while current:
                  stack.append(current)
                  current = current.left

              # Process the leftmost unvisited node
              current = stack.pop()
              count += 1

              # Check if this is the kth smallest
              if count == k:
                  return current.val

              # Move to the right subtree
              current = current.right

          return -1  # Should never reach here if k is valid
    explanation: |
      **Time Complexity:** O(H + k) — Same as recursive approach.

      **Space Complexity:** O(H) — Explicit stack replaces recursion stack.

      The iterative approach uses an explicit stack to simulate recursion. We push nodes while going left, pop to visit, then move right. This gives more control over termination and avoids potential stack overflow for very deep trees.

  - approach_name: Collect and Sort (Suboptimal)
    is_optimal: false
    code: |
      def kth_smallest(root: TreeNode | None, k: int) -> int:
          values = []

          def collect(node: TreeNode | None) -> None:
              if not node:
                  return
              # Collect all values via any traversal
              values.append(node.val)
              collect(node.left)
              collect(node.right)

          collect(root)
          values.sort()
          return values[k - 1]  # k is 1-indexed
    explanation: |
      **Time Complexity:** O(n log n) — Collecting is O(n), sorting is O(n log n).

      **Space Complexity:** O(n) — Store all n values.

      This approach ignores the BST property entirely. It collects all values, sorts them, and returns the k<sup>th</sup> element. While correct, it's inefficient and doesn't leverage the fact that inorder traversal of a BST is already sorted. Included to illustrate why understanding data structure properties matters.
