skip to content

A binary search tree delete replaces a two-child node with its right child — what breaks?

level: middleimportance: must knowfreq 70%

answer

  1. Count the keys before and after
  2. One parent slot, two subtrees to rehome
  3. An ordering check will not notice
  4. Which single key belongs between the two subtrees
  5. Successor is leftmost, so no left child

basics

~20 s

Every key in the deleted node's left subtree disappears from the tree, because the returned right child never adopts it. The result is still a perfectly valid search tree, just missing keys, so lookups quietly report stored keys as absent.

solid answer

~50 s

Promoting the right child throws away the entire left subtree: those keys become unreachable, yet the tree that remains is still correctly ordered, so nothing complains. That is the nasty part — a structural check passes, and the only symptom is a lookup reporting 'not present' for a key that was inserted. The correct two-child delete uses the node's **inorder successor**: the smallest key in its right subtree, found by walking left from the right child. Copy that key into the node being deleted, then delete the successor from the right subtree. The successor is the unique key larger than everything on the left and smaller than everything else on the right, so both subtrees stay valid beneath it — and it is easy to remove, since being leftmost it has no left child. The predecessor works identically.

code

pseudocode · 9 lines
pseudocode
// remove key from the subtree rooted at node; return the new subtree root
delete(node, key):
  if node == NIL: return NIL
  if key < node.key:  node.left  = delete(node.left, key);  return node
  if key > node.key:  node.right = delete(node.right, key); return node
  // node.key == key
  if node.left  == NIL: return node.right
  if node.right == NIL: return node.left
  return node.right          // <-- both children exist: left subtree is dropped

go deeper

for a junior

Know that deleting a node with two children is the special case, and that the replacement comes from inside one of its subtrees rather than being one of its children promoted wholesale.

for a middle

Explain why exactly one key belongs in the vacated slot, how the leftmost walk of the right subtree finds it, and why removing that successor afterwards is the easy case.

for a senior

Treat this as a review scenario: state that the dropped-subtree bug leaves a valid-looking tree, name the test that catches it (count and round-trip lookups, not a structural assertion), and mention the node-identity issue when keys are copied rather than nodes relinked.

for a principal

Decide the contract: does deletion preserve node identity for outside holders such as iterators and cached handles? Copying keys is simpler; relinking nodes is what a structure with external references must do, and that choice belongs in the design, not in a patch.

## The three cases of delete Deleting from a binary search tree splits into cases by how many children the doomed node has. - **No children.** Detach it. Nothing below it needs a home. - **One child.** Splice: the child takes the node's place under its grandparent. This is safe because the whole child subtree already sits on the correct side of every ancestor — removing an intermediate node does not change any key's relationship to anything above it. - **Two children.** Neither child can simply be promoted, and this is the only interesting case. ## Why promoting a child is wrong A node has one slot in its parent. It has two subtrees. Promoting the right child hands that slot to the right subtree, and the left subtree — every key in it — is dropped on the floor. In a reference-counted or garbage-collected setting the memory quietly goes away; in a manually managed one it leaks. Either way the keys are gone from the structure. What makes this bug hard to see is that **the surviving tree is still perfectly valid**. Every remaining key is still in the right relationship to every ancestor, because deleting keys never breaks the ordering rule. So an ordering check over the tree passes. A shape or height check passes. A debugger view looks unremarkable. The only observable symptom is that a lookup for a key from the discarded subtree reports 'not present' — and if the caller treats absence as a legitimate answer, the corruption can sit in production indefinitely. The tests that catch it are behavioural: insert a known set, delete a middle key, assert the count and assert every other key is still retrievable. ## The inorder successor, and why it is the right replacement The key you want in the vacated position is one that is simultaneously **greater than everything in the left subtree** and **less than everything remaining in the right subtree**. Exactly one key in the tree satisfies that: the smallest key in the right subtree — the inorder successor of the node being deleted. Find it with a leftmost walk starting at the right child. The procedure is then: 1. Locate the node to delete by the usual descent. 2. If it has two children, walk to the leftmost node of its right subtree — the successor. 3. Copy the successor's key (and payload) into the node. 4. Delete the **successor** from the right subtree. Step 4 is not a recursion into the same hard case. The successor is leftmost in its subtree, so by definition it has no left child, which means it falls into the no-child or one-child case and terminates immediately. That is why the algorithm is only ever "one hard case plus one easy case", never an unbounded chain. The mirror-image choice is equally correct: take the **inorder predecessor**, the largest key in the left subtree, found by a rightmost walk. It satisfies the same betweenness condition from the other side and is just as cheap to remove, since being rightmost it has no right child. Implementations pick one and stick to it; some deliberately alternate between successor and predecessor so that long delete-heavy runs do not repeatedly thin the same side of the tree. ## A worked check Take a tree with root `50`, left subtree holding `30, 20, 40` and right subtree holding `70, 60, 80`. Delete `50`. The successor is the leftmost node of the right subtree, `60`. Copy `60` into the root, then delete the old `60` from the right subtree — it is a leaf, so it just detaches. The result has root `60`, left subtree `30, 20, 40`, right subtree `70` with only `80` under it. Check the invariant: everything on the left is below `60`, everything on the right is above it, and all seven remaining keys are still present. Now compare with the buggy version: promoting the right child would have made `70` the root and silently vaporised `30`, `20` and `40`. ## Cost, and a note on what is copied Deletion is `O(h)`: one descent to find the node, plus at most one further descent down the right subtree's leftmost spine, and those two paths together are still bounded by the height. Nothing scans. One practical wrinkle: copying the successor's *key* into the surviving node means the node object holding the deleted key survives while the node object holding the successor's key is destroyed. If anything outside the tree holds a reference to a node — an iterator, an index, a cached handle — that reference now points at a node whose key changed under it. Implementations that must keep node identity stable therefore relink pointers instead of copying contents: the successor node is unhooked from its position and spliced in as the actual replacement, adopting both subtrees. Same algorithm, more pointer surgery, no key copying. ## What a strong answer sounds like Say that promoting a child loses a whole subtree, stress that the corruption is invisible to an ordering check and only shows up as a missing key, then give the successor rule and the reason it is the unique correct replacement — and finish with why removing the successor is the easy case.

  • Why is deleting the successor itself not a recursion back into the hard case?
    Because the successor is the leftmost node of the right subtree, so it has no left child by construction. That puts it in the no-child or one-child case, which resolves by detaching or splicing. The algorithm therefore does one hard case and one easy case, never a chain of hard ones.
  • Would the inorder predecessor work just as well?
    Yes — the largest key in the left subtree satisfies the same betweenness condition from the other side, and being rightmost it has no right child, so removing it is equally easy. Pick one convention and apply it consistently; some implementations alternate so repeated deletes do not keep thinning the same side.
  • What test catches the dropped-subtree bug that a structural check misses?
    A behavioural one. Insert a known key set, delete a node you know has two children, then assert the remaining count and look up every surviving key. The ordering rule still holds after the bug, so only presence and count reveal it. An inorder walk compared against the expected sorted remainder works too.
  • Is the successor ever simply the deleted node's right child?
    Yes, exactly when that right child has no left child of its own — then the leftmost walk stops immediately at it. That special case is why the buggy code sometimes appears to work in a hand-run test, which is precisely what makes the bug survive review.

Removing a middle manager by promoting only one of their two teams does not reorganise the department; it deletes the other team from the org chart, and the chart still looks tidy.

saying these in an interview costs you the question

  • Says promoting the right child breaks the ordering rule
  • Claims the inorder successor is always a leaf
  • Thinks the successor is always the node's right child
  • Believes a validity check would catch the dropped subtree
  • Says the predecessor cannot be used as the replacement

context