A plain trie must choose how each node stores its children. A σ-wide array wastes slots on sparse branches. A Dictionary<char, Node> stores only real branches but hashes each character and loses the array’s natural order. A ternary search tree (TST) uses a small binary search tree keyed on the next character instead. Each node has three pointers, and character order survives.

Each node carries one split character and three links: lo for a smaller current character, hi for a larger one, and eq for an equal one. Only eq advances to the next character. A lookup moves sideways through lo or hi until the split matches, then moves one character deeper through eq. The key path is threaded through the eq links. lo and hi replace a trie’s child lookup with comparisons.

Compared with an array-backed trie, the TST reserves no slot for an absent symbol. It keeps lexicographic order and supports prefix or near-neighbour queries, but its per-position comparison trees can become unbalanced.

Core shape: trie positions linked by eq. At each position the alternatives form a BST split on the character via lo/hi → three pointers per node, not σ

Visualization

Representation and Invariants

A node holds a split character, an end-of-key flag, and three child links:

  • Split — the character this node discriminates on.
  • Lo / Hi — subtrees for keys whose character at this same position sorts before / after Split. Following them does not consume a character.
  • Eq — the subtree for the next position, taken only after the current character equals Split. Following it consumes one character.
  • IsEnd — true when the eq-chain from the root to this node spells a complete stored key.

The key is never stored. cat is present when, starting at the root, three matched-then-eq steps land on a node whose Split is t and whose IsEnd is set. A single trie level — “which character comes next here?” — is exactly one BST reachable through lo/hi links, and the answer to that question is the eq link out of the matching node.

Three invariants hold:

  1. BST order within a position. For any node, every Split in its Lo subtree is smaller and every Split in its Hi subtree is larger, both compared at the same string position. This is what makes an in-order walk of lo/eq/hi emit keys in sorted order.
  2. Eq is the only depth-advancing link. The number of eq links from the root to a node equals that node’s character position. Lo and Hi stay at the current position; Eq moves forward exactly one.
  3. IsEnd is independent of children. car and cart coexist: the node spelling car is flagged and still has an eq subtree carrying on to t.

The whole contract lives in the difference between “matched the split and there is more key” (follow eq) and “the character is smaller or larger” (follow lo/hi without advancing).

Complexity
  • Search hit (key length m): O(m + log n) avg, O(m + n) worst
  • Search miss: O(m + log n) avg, O(m + n) worst
  • Insert: O(m + log n) avg, O(m + n) worst
  • Prefix collection: O(m + log n + k) avg, O(m + n + k) worst
n
number of keys stored in the tree
m
length of the inserted or queried key
k
total characters emitted by prefix collection
s
total characters across all stored keys

Where the Three-way Split Earns Its Place

The lo/eq/hi structure is not just a memory trick: it preserves character order while keeping fan-out fixed at three pointers per node.

  • Sorted output for free. An in-order traversal — recurse lo, visit the eq subtree with Split appended, recurse hi — emits every key in lexicographic order without a separate sort. A Dictionary-backed trie has to collect and sort its children at each node to do the same.
  • Near-neighbour and wildcard search. The comparison layout supports partial-match (.a.-style wildcards) and one-substitution spell-check queries: at a wildcard or an allowed-mismatch position, explore the alternative branches. Elsewhere, follow only the matching branch. A hash-map trie can perform the same search by iterating its actual children, so this is not an asymptotic TST advantage. Insertion and deletion edits need extra query-index state in either structure because they change which character positions align.
  • Bounded fan-out. Three pointers per node means a TST is often smaller than a Dictionary<char, Node> trie after including the hash table’s own overhead per node, while avoiding the array trie’s σ reservation entirely.

Where it breaks is balance. Randomising insertion order gives only the expected average-case bound listed in the Complexity tab. It does not guarantee that bound. A deterministic log n term requires balancing the per-position BSTs. And like any prefix structure, a TST only pays off when keys share prefixes and have a meaningful character sequence. Opaque integer or float keys gain nothing from it.

Diagram and C# Implementation

Comparison

Every structure below stores a set of string keys. They differ in the per-node child representation and what that costs.

StructureCharacter routingPrefix / ordered supportStorage shapeStronger case
Ternary search treeCompares the next character through lo/eq/hiNative prefix walk. In-order traversal is sortedOne character and three child pointers per nodeLarge or unknown alphabets with ordered output
Array-backed TrieIndexes a fixed child slotNative prefix walk. Symbol-order traversal is sortedReserves one child array at every nodeSmall fixed alphabets where direct indexing matters
Hash-map TrieHashes the next characterNative prefix walk. Sorting requires ordered child keysAllocates only present children plus map overheadSparse large alphabets without TST shape sensitivity
Radix / PATRICIA trieCompares substring-labelled edgesNative prefix walk. Sorted output requires symbol-ordered edgesCompresses single-child runs and stores edge labelsLong keys with many non-branching runs
Balanced Binary Search TreeCompares complete keysOrdered and range scans. Prefix search needs bounded key rangesStores one complete key per nodeTotal order over complete keys without shared-prefix structure

A radix trie wins when node count is the constraint and keys are long and sparse. A balanced BST keyed on whole strings is the choice when there are no shared prefixes to exploit and total order over complete keys is all that’s needed.

References