BeeTool.Dev - Essential Tools for developers

Stories.Tool Tabs VaultPersonal Documents
Super Debugging Context
BeeTool.Dev

Essential tools, knowledge, and playgrounds for developers.

Explore

  • Stories
  • Personal Documents
  • Features
  • Algorithms Playground

Tool Tabs

  • JSON Compare
  • SQL Formatter
  • Text Diff
  • All tools →

Learn

  • Sorting algorithms
  • Searching algorithms
  • Algorithm Race Mode
© 2024 – 2026 BeeTool.DevPrivacy policy
← Algorithms Playground
Searching · Advanced

Trie Search

Search time depends on the length of the key, not how many keys you've stored — a dictionary of a million words answers in exactly as many steps as the word has letters.

AverageO(m)StructureTrieNeeds sortedNoComparisonYes
•catrdetdogtneor
Must remember

The key IS the path: one tree edge per character, a flag marks where real words end.

Search O(m) for key length m · independent of n keys · Space O(alphabet · nodes)

Hands on Interactive visualization

Word
card
Or type
Stored: cat, car, card, care, cart, dog, dot, done, door
•catrdetdogtneor
0/0Ready — press play
Speed
Step 0 / 0
Comparisons
0
Swaps
0
Reads
0
Writes
0
Elapsed
0.0s
Frames
0
Depth
0 / 0
Aux memory
0 B

Implementation Code

class TrieNode {
  children = new Map<string, TrieNode>();
  isEnd = false; // marks a stored word, not just a prefix
}

export class Trie {
  private root = new TrieNode();

  insert(word: string): void {
    let node = this.root;
    for (const ch of word) {
      if (!node.children.has(ch)) node.children.set(ch, new TrieNode());
      node = node.children.get(ch)!;
    }
    node.isEnd = true;
  }

  // One node per character: O(word.length), independent of size.
  search(word: string): boolean {
    const node = this.walk(word);
    return node !== null && node.isEnd; // word, not just prefix
  }

  startsWith(prefix: string): boolean {
    return this.walk(prefix) !== null; // same walk, no flag check
  }

  private walk(s: string): TrieNode | null {
    let node = this.root;
    for (const ch of s) {
      const next = node.children.get(ch);
      if (!next) return null; // path breaks
      node = next;
    }
    return node;
  }
}

Big-O Complexity

Best
O(1)
Average
O(m)
Worst
O(m)
Space
O(alphabet · nodes)

m is the key length — lookup cost is independent of the number of stored keys. Space is the pain point: fixed child arrays cost alphabet-size slots per node even when nearly empty; child maps shrink memory but slow each hop. Compressed variants (radix/Patricia tries) merge single-child chains.

In production When to use

Autocomplete & typeahead

Search boxes descend the trie along the typed prefix in O(m), then enumerate the subtree for suggestions — every key sharing that prefix lives below one node, so no scanning of unrelated entries.

IP routing (longest-prefix match)

Routers store CIDR prefixes in binary tries and walk an address bit by bit, remembering the last node with a route — the deepest match wins. Longest-prefix matching is the trie's signature move.

Spell checkers & word games

Dictionary membership is O(word length), and prefix pruning turns Boggle/word-search style DFS from exponential to tractable — abandon a board path the moment its prefix leaves the trie.

T9 & keyboard prediction

Predictive text walks a trie keyed by digit or letter sequences; each keypress advances one node, and the subtree below ranks the candidate completions.

Prefix-based billing & telecom routing

Call-rate tables keyed by dialing prefixes (country/carrier codes) resolve a number to its longest matching prefix — the same longest-prefix walk as IP routing, applied to tariffs.

Multi-pattern string matching

Aho-Corasick builds a trie of all patterns plus failure links to scan text for thousands of patterns simultaneously in one pass — the engine inside intrusion-detection systems and content filters.

Trade-offs Strengths & weaknesses

  • O(m) lookup independent of the number of keys — n can grow without slowing search.
  • Prefix queries are structural: all keys with a given prefix sit in one subtree.
  • Longest-prefix match falls out of a single walk — awkward or impossible for hash tables.
  • No hash collisions, no comparator subtleties: the key's characters ARE the path.
  • Shared prefixes are stored once, compressing datasets with heavy prefix overlap.
  • Ordered traversal yields keys in lexicographic order for free.
  • Memory-hungry: sparse fixed-size child arrays waste most slots; pointer overhead dwarfs the characters stored.
  • Cache-hostile — every character hop is a pointer dereference to a scattered node.
  • For pure equality lookup of whole keys, a hash table is simpler and usually faster in practice.
  • Large or unbounded alphabets (full Unicode) force child maps or bytewise decomposition — more design decisions.
  • Deletion must prune now-childless chains upward, which is easy to get wrong.
  • Naive tries store one character per node; production uses compressed radix/Patricia variants for real workloads.

Intuition Mental model

The phone-keypad autocomplete tree

Type on a phone keyboard: each letter narrows the suggestions, because the keyboard is walking a tree where every branch is one letter and every path from the root spells a prefix. Words that share a beginning share the same trunk — the tree only splits where the words do.

  1. The empty search box = the root: every stored word is still possible below you.
  2. Typing 'c' takes the c-branch = following the child pointer for that character.
  3. 'ca' then 'car' descend further = each keypress is one O(1) hop; total cost is the word's length.
  4. Suggestions shown = the subtree below your current node — exactly the words with your prefix, nothing else.
  5. 'car' lights up as a word while 'ca' doesn't = the end-of-word flag: both are nodes on the path, only one is marked.

Prep Interview questions

Implement a trie with insert, search, and startsWith. What separates the last two?
Both walk the trie one character at a time and fail on a missing child. search additionally requires the final node's is-end-of-word flag; startsWith only requires the walk to complete. One boolean is the entire difference.
Design search with '.' wildcards (matches any one character).
A wildcard means you can't pick one child — branch into ALL children at that position, i.e. DFS/backtracking over the trie. Cost is O(m) for literal-only queries but can touch many branches per dot; memoization rarely helps because paths differ.
How does longest-prefix match work, and why do routers use tries for it?
Walk the key from the root, and every time you pass a node marked as a stored prefix, record it as best-so-far; when the walk dies, return the last recorded match. Hash tables would need one lookup per candidate prefix length — the trie does all lengths in a single walk.
Your trie of 100k lowercase words uses gigabytes. Diagnose and fix.
Fixed 26-pointer arrays per node waste ~25 slots on average at 8 bytes each. Fixes in order of impact: child hash maps for sparse nodes, radix/Patricia compression merging single-child chains into string edges, or array-backed nodes with indices instead of pointers.
Word Search II: find all dictionary words in a letter grid. Why is a trie the key optimization?
Build a trie of the dictionary, then DFS the grid advancing the trie node alongside the path — the moment the current cell has no child in the trie, prune the whole branch. Without the trie you'd re-check every dictionary word per path; with it, dead prefixes die instantly.

Watch out Common bugs

Missing the end-of-word flag

Storing 'application' creates a path through 'app' — but 'app' is only a word if some node is explicitly flagged. Treating any reachable node as a match makes every prefix of every key a false positive; search must check isEnd, startsWith must not.

Fixed 26-child arrays exploding memory

An array-of-26 per node is fast but allocates 26 pointers even for chains where each node has one child — sparse datasets waste over 90% of slots. Use a map per node, or a compressed radix trie, once memory is measured and matters.

Unicode and case handling

Indexing children by ch - 'a' crashes or corrupts on uppercase, accents, or emoji. Normalize input up front (case-fold, NFC normalization) and choose the alphabet deliberately — bytes, code points, or a validated character class.

Delete leaving orphan chains — or over-pruning

Just clearing isEnd leaks the now-useless chain of nodes; but pruning must stop at any node that is still a word end or still has other children, or deleting 'app' destroys 'application'. Unwind from the leaf, removing only childless non-terminal nodes.

One screen Cheat sheet

The key IS the path: one tree edge per character, a flag marks where real words end.

Search O(m) for key length m · independent of n keys · Space O(alphabet · nodes)
  • Lookup cost scales with key length, not collection size.
  • isEnd flag distinguishes a stored word from a mere prefix — the #1 bug source.
  • Prefix queries and longest-prefix match are single walks; hash tables can't do either cheaply.
  • Child storage trade-off: fixed arrays (fast, fat) vs maps (lean, slower) vs radix compression.
  • Normalize case/Unicode before indexing children.
  • Delete = clear the flag, then prune childless non-terminal nodes upward — carefully.
Pick whenQueries are prefix-shaped — autocomplete, longest-prefix match, lexicographic enumeration — or keys share heavy prefixes.
Avoid whenYou only test whole-key equality (hash table), memory is tight with sparse keys, or keys are long with no shared prefixes.

Binary Search Tree Search

Breadth-First Search (BFS)