Skip to main content
Scenario Article

Tries (Prefix Trees): The Data Structure Behind Autocomplete

Three Classic Trie Problems with Full Java Solutions

Abstract Algorithmsβ€’Mar 29, 2026
XLinkedInReddit
πŸ’‘ Executive Summary

A Trie stores strings character by character in a tree, so every string sharing a common prefix shares those nodes.

🎯 System Scenario & Requirements

System Goal

A Trie stores strings character by character in a tree, so every string sharing a common prefix shares those nodes.

Actors & Components

  • Application or API tier
  • Core service under design
  • Operator or SRE observer

Functional Requirements

  • TLDR: A Trie stores strings character by character in a tree, so every string sharing a common prefix shares those nodes.
  • Insert and search are O(L) where L is the word length.
  • Tries beat HashMaps on prefix queries β€” startsWith in O(L) with zero collision overhead.

Constraints & Assumptions

  • search vs startsWith differ only in the final check.
  • The bug is checking isEndOfWord in startsWith (which rejects valid prefixes that don't end a word) or forgetting to check it in search (which falsely reports prefixes as words).

⚑ Execution Stages & Decision Trace

1

Stage 1 β€” How Google Autocomplete Responds in Microseconds

Action / Decision:

Google's autocomplete suggests completions as you type β€” in milliseconds, across a vocabulary of billions of words.

Component Behavior:

The question is not just "does this word exist?" β€” it is "give me every word that starts with this prefix." A HashMap answers the first question in O(1) but answers the second in O(n) by scanning every entry.

Observable Output: A Trie answers both in O(L), where L is the length of the prefix.
Read Full Stage in Deep Dive ↓
πŸ“–

Deep Dive

Detailed Architectural Analysis

Scenario context

A Trie stores strings character by character in a tree, so every string sharing a common prefix shares those nodes. Insert and search are O(L) where L is the word length. Tries beat HashMaps on prefix queries β€” startsWith in O(L) with zero collision overhead. The canonical interview implementation uses a 26-element children array and an isEndOfWord flag.


Example 1: Implement Trie β€” LeetCode 208

The…

This walkthrough focuses on Three Classic Trie Problems with Full Java Solutions.

Before making a design choice, clarify:

  • Example 1: Implement Trie β€” LeetCode 208 The full Trie class above is the canonical solution.
  • In interviews, emphasize the search vs startsWith distinction: both walk the same path, but search requires isEndOfWord == true at the final node.
  • Example 2: Word Search II β€” Find All Words in a Board (LeetCode 212) Build a Trie from the word list, then DFS every board cell.

Concepts in play: tree-data-structures, jvm-internals-and-garbage-collection, sorting-and-searching-algorithms, hash-tables-and-bloom-filters

Stage 1 β€” How Google Autocomplete Responds in Microseconds

  • Google's autocomplete suggests completions as you type β€” in milliseconds, across a vocabulary of billions of words.
  • The question is not just "does this word exist?" β€” it is "give me every word that starts with this prefix." A HashMap answers the first question in O(1) but answers the second in O(n) by scanning every entry.
  • A Trie answers both in O(L), where L is the length of the prefix.
  • The Trie's secret is structure.

Observable outcome: The Trie's secret is structure.

Stage 2 β€” Each Node Is a Character: The Trie Mental Model

  • The intuition: each node in a Trie represents one character position in a string.
  • The root represents the empty string.
  • Every edge from a parent to a child represents one character.
  • A path from the root to a node spells out the prefix formed by all edges on that path.

Observable outcome: A path from the root to a node spells out the prefix formed by all edges on that path.

Stage 3 β€” TrieNode and Trie in Java: Insert, Search, and StartsWith

  • The canonical implementation uses a fixed-size children array of 26 slots (one per lowercase English letter) rather than a HashMap<Character, TrieNode .
  • The array uses more memory per node but gives O(1) child lookup with no hash computation or collision handling.
  • The critical distinction between search and startsWith : both walk the same path character by character.
  • They differ only in the final check.
public class Trie {

    // Each node holds 26 possible children (a–z) and an end-of-word marker
    private static class TrieNode {
        TrieNode[] children = new TrieNode[26];
        boolean isEndOfWord = false;
    }

    private final TrieNode root = new TrieNode();

    // Insert a word into the Trie β€” O(L) where L = word.length()
    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            int idx = c - 'a';
            if (node.children[idx] == null) {
                node.children[idx] = new TrieNode();   // Create node on demand
            }
            node = node.children[idx];
        }
        node.isEndOfWord = true;    // Mark end of this word
    }

    // Search for an exact word β€” returns true only if word was inserted
    public boolean search(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            int idx = c - 'a';
            if (node.children[idx] == null) return false;  // Prefix not found
            node = node.children[idx];
        }
        return node.isEndOfWord;  // Prefix exists; check it ends a word
    }

    // Check if any inserted word starts with this prefix
    public boolean startsWith(String prefix) {
        TrieNode node = root;
        for (char c : prefix.toCharArray()) {
            int idx = c - 'a';
            if (node.children[idx] == null) return false;
            node = node.children[idx];
        }
        return true;   // Reaching here means the prefix path exists
    }
}

Observable outcome: They differ only in the final check.

Stage 4 β€” Deep Dive: Trie Memory Layout and Prefix Query Performance

  • Trie Internals: The children Array and isEndOfWord Flag Each TrieNode object in the canonical implementation holds: - children[26] : 26 object references (8 bytes each on 64-bit JVM with compressed OOPs = 208 bytes per…
    • isEndOfWord : 1 boolean (1 byte, padded to 8 bytes in JVM).
  • A Trie for 10,000 English words can easily use 5–50 MB of heap space β€” far more than a HashSet<String for the same words.
  • This is the main cost of the fixed-array Trie.

Observable outcome: This is the main cost of the fixed-array Trie.

Stage 5 β€” Trie Structure for car, card, care, cart, cat

  • A visual walkthrough of exactly which nodes are created and which share prefixes: Words stored: car (R node), card (D node), care (E node), cart (T cart node), cat (T cat node).
  • The five words use 8 nodes total instead of 5 separate hash table entries.
  • The prefix ca is shared by all five words β€” it costs 2 nodes regardless of how many words share it.
  • A startsWith("car") query walks 3 nodes and returns true without touching any other word's nodes.
graph TD
    Root([(root)])
    C([c])
    A([a])
    R([r  isEnd])
    T_cat([t  isEnd])
    D([d  isEnd])
    E([e  isEnd])
    T_cart([t  isEnd])

    Root --> C
    C --> A
    A --> R
    A --> T_cat
    R --> D
    R --> E
    R --> T_cart

    style Root fill:#607D8B,color:#fff
    style C fill:#2196F3,color:#fff
    style A fill:#2196F3,color:#fff
    style R fill:#4CAF50,color:#fff
    style T_cat fill:#4CAF50,color:#fff
    style D fill:#FF9800,color:#fff
    style E fill:#FF9800,color:#fff
    style T_cart fill:#FF9800,color:#fff

Observable outcome: A startsWith("car") query walks 3 nodes and returns true without touching any other word's nodes.

Stage 6 β€” Where Tries Power Real Systems

  • Type-ahead search in search engines: Google, Bing, and Elasticsearch all use trie-based prefix indexes for autocomplete suggestions.
  • The trie is typically augmented with a frequency score at each terminal node so the autocomplete system can return the most popular completions first (Priority Queue + Trie = Search Autocomplete System).
  • IP routing β€” longest prefix match: Network routers match destination IP addresses against routing table entries using a binary trie where each bit of the IP address is one level of the tree.
  • The router walks the trie bit by bit, keeping track of the most specific route (longest prefix) that matches.

Observable outcome: The router walks the trie bit by bit, keeping track of the most specific route (longest prefix) that matches.

End-to-end outcome

After working through the stages, you should be able to explain:

    • A Trie stores strings character by character , sharing nodes for all strings with a common prefix.
  • This structural sharing makes prefix queries O(L) regardless of how many strings are stored.

Alternatives considered

Trie vs HashMap: When Prefix Queries Tip the Balance

The Trie vs HashMap decision is not about which is "better" β€” it is about which operation dominates your workload.

When to Use Trie vs HashMap vs Sorted Array

When to Use Trie vs HashMap vs Sorted Array

Failure and degraded modes

Lessons Learned: Trie Implementation Pitfalls

  • Detection: search vs startsWith differ only in the final check.
  • User or operator impact: search vs startsWith differ only in the final check.

Share this case study

Discuss architecture trade-offs and lessons with your team.

Share:XLinkedInReddit