A bounds-based search-tree validator rejects a valid tree holding the most negative key — why?
answer
- the root has to start somewhere
- what does the initial range represent
- a real key can equal your sentinel
- no single comparison suits sentinel and real bounds
- absent bound versus extreme bound
basics
~20 sThe recursion seeded its initial bounds with the key type's extreme representable values, so a real key equal to that extreme is indistinguishable from being out of range. Make the bounds optional and skip absent comparisons, or use a bound type wider than the key type.
solid answer
~50 sBounds validation compares each key against an inherited range, and the root has to start with something. Seeding that range with the smallest and largest representable values of the key type looks harmless until a node legitimately holds one of them: with a `key <= low` rejection test the extreme key fails, and switching to `key < low` to save it silently weakens the real bounds deeper in the tree so equal keys slip past. There is no comparison that is correct for both the sentinel and the genuine bounds. The clean fixes are to model each bound as present-or-absent and skip the comparison when absent, or to carry bounds in a type strictly wider than the keys, or to pass the constraining ancestor nodes rather than raw values. The same trap appears in the streaming inorder form when the previous key is seeded with a sentinel instead of a first-visit flag.
go deeper
Know that the root starts with no constraint at all, and that representing 'no constraint' as a real number is what creates the problem. Being able to name the failing input is enough here.
Explain why neither strict nor non-strict comparison fixes the seed, and show the optional-bound version where an absent bound simply skips its comparison.
Show how you would have caught it: boundary-value inputs at the extremes of the key domain and a property test that builds trees from random permutations, rather than hand-written mid-range fixtures.
Own the duplicate-key policy as a written contract across insert, search, delete and validation, and decide where that contract is enforced so two code paths cannot drift apart unnoticed.
## The bug is in the seed, not in the recursion The bounds formulation of search-tree validation is correct: each node is checked against a range inherited from its ancestors, the left call tightens the ceiling and the right call raises the floor. The root, however, is unconstrained, and that unconstrained state has to be represented somehow. Reaching for the key type's minimum and maximum representable values is the natural move and the source of a whole family of false rejections. ### Why no choice of comparison saves it Suppose keys are signed integers and the recursion starts with low = the most negative representable value, high = the largest. The body rejects when `key <= low` or `key >= high`. - A single-node tree whose key is the most negative representable value is **valid**, but `key <= low` is true, so it is rejected. Same at the top of the range. - Loosen the test to `key < low` and the sentinel case passes — but now the *genuine* bounds passed down the recursion are compared non-strictly, so a descendant whose key exactly equals a constraining ancestor is accepted. That is a false acceptance, and a worse bug than the one you removed, because it fires on ordinary data rather than at the extremes. The conflict is structural: the sentinel wants a permissive comparison and the real bounds want a strict one, and a single operator cannot be both. Any fix has to distinguish "no constraint" from "a constraint that happens to be extreme". ### Fixes, best first 1. **Optional bounds.** Model each side as present-or-absent and skip the comparison entirely when absent. The root passes both as absent; every tightened bound is by construction a real key. This is exact for every key type, including ones with no natural sentinel at all such as strings or composite keys, and it is the version to reach for by default. 2. **Widen the bound type.** Carry bounds in a type whose range strictly contains the key type's, so sentinels lie outside anything a key can be. This works, and it is a common quick fix, but it is fragile: it silently stops working the day the key type is widened to match, and it does not generalise to non-numeric keys. 3. **Pass ancestor references instead of values.** Hand down the nearest constraining ancestor node on each side, treating an absent ancestor as unconstrained. Mechanically identical to optional bounds, and it reads well when the comparison is a custom ordering rather than `<`. 4. **Streaming inorder with a first-visit flag.** The inorder formulation — keys must arrive strictly increasing — has exactly the same trap if you seed the previous key with an extreme value: a first key equal to that sentinel is wrongly rejected. Track "have we emitted anything yet" as a separate flag and skip the comparison on the first key. ### The other half of the boundary question: equal keys A validator cannot be written until someone answers what equal keys mean in this structure, and the answer must match what insert, search and delete do: - **Duplicates rejected.** Both comparisons are strict, and validation fails on any repeated key. Simple, and usually right when keys identify records. - **Duplicates allowed on one designated side.** Pick a side once and apply it everywhere — say equal keys always go right. Then the right-hand comparison becomes non-strict while the left stays strict, and every other operation must follow the same convention or lookups will miss records that are physically present. - **Duplicates collapsed into a count on the node.** The tree stays strictly ordered and multiplicity lives in the payload. Validation is strict on both sides, and range queries stay simple. The failure mode when the policy is unwritten is that different code paths disagree: one path inserts equal keys left, another searches right, and records become unreachable without any structural corruption a validator would catch. Writing the policy into the validator makes it executable documentation. ### Testing this properly Example-based tests will not find the sentinel bug because nobody writes a fixture with an extreme key by hand. Two habits do find it: include the extreme representable values of the key domain in a boundary-value test set, and property-test the invariant — build a tree by inserting a random permutation of keys, assert it validates, then perturb one node's key and assert it does not. Both are cheap, and both catch the class rather than the instance.
- How does the streaming inorder version of the check hit the same trap?It keeps the previously emitted key and requires each next key to be strictly greater. Seed that previous key with an extreme sentinel and a first key equal to the sentinel is wrongly rejected. The fix is the same shape: carry a first-visit flag and skip the comparison for the very first key rather than inventing a value it must beat.
- If equal keys are allowed on the right, what changes in the bounds comparison?The lower bound becomes non-strict on that side: a right descendant may equal the ancestor that raised the floor, so the rejection test there is `key < low` rather than `key <= low`, while the upper-bound test stays strict. Search, insert and delete must use the identical convention, or equal keys become unreachable.
- Why prefer optional bounds over simply using a wider type for the bounds?Optional bounds are exact for every key domain, including strings, tuples and custom orderings that have no representable extremes at all, and they cannot be invalidated by a later change to the key type. The wider-type trick works only for numeric keys and quietly breaks the day the key type is widened to match the bound type.
saying these in an interview costs you the question
- Insists extreme sentinel values are always safe bounds
- Loosens both comparisons to save the sentinel case
- Cannot say which side may hold keys equal to the parent
- Uses one duplicate convention in insert and another in search
- Tests only mid-range keys and never the domain boundaries