Skip to content
intermediatePhase 4 · Trees & Heaps

Trie

Master prefix trees for string searching and autocomplete systems.

1h
5 problems
Topic Progress0%

Trie Fundamentals

A trie (prefix tree) is a tree-like data structure for storing strings. Each node represents a character, and paths from root to marked nodes form words.

Node Structure

class TrieNode {
    TrieNode[] children;
    boolean isEndOfWord;

    public TrieNode() {
        children = new TrieNode[26];  // lowercase letters
        isEndOfWord = false;
    }
}

Visual Example

Insert: "apple", "app", "banana"

        (root)
       /      \
      a        b
      |        |
      p        a
     / \\       |
    p   n      n
    |   |      |
    l   a      a
    |   |      |
    e*  a*     n*

* = end of word marker

Trie Class Implementation

class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // Insert a word - O(m) where m = word length
    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEndOfWord = true;
    }

    // Search for exact word - O(m)
    public boolean search(String word) {
        TrieNode node = searchPrefix(word);
        return node != null && node.isEndOfWord;
    }

    // Check if any word starts with prefix - O(m)
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }

    private TrieNode searchPrefix(String prefix) {
        TrieNode node = root;
        for (char c : prefix.toCharArray()) {
            int index = c - 'a';
            if (node.children[index] == null) return null;
            node = node.children[index];
        }
        return node;
    }
}

Delete Operation

public void delete(String word) {
    deleteHelper(root, word, 0);
}

private boolean deleteHelper(TrieNode node, String word, int index) {
    if (index == word.length()) {
        if (!node.isEndOfWord) return false;
        node.isEndOfWord = false;
        return isEmpty(node);  // Can delete this node if no children
    }

    int charIndex = word.charAt(index) - 'a';
    TrieNode child = node.children[charIndex];
    if (child == null) return false;

    boolean shouldDeleteChild = deleteHelper(child, word, index + 1);

    if (shouldDeleteChild) {
        node.children[charIndex] = null;
        return isEmpty(node) && !node.isEndOfWord;
    }
    return false;
}

private boolean isEmpty(TrieNode node) {
    for (TrieNode child : node.children) {
        if (child != null) return false;
    }
    return true;
}

Complexity

Operation Time Space
Insert O(m) O(m)
Search O(m) O(1)
Delete O(m) O(1)
startsWith O(m) O(1)

where m = length of word/prefix

Space: O(N × m) total where N = number of words, m = avg length. In practice, shared prefixes save space.

Trie Applications

Autocomplete / Word Search II

Find all words in trie with given prefix:

public List<String> autocomplete(TrieNode root, String prefix) {
    List<String> results = new ArrayList<>();
    TrieNode node = root;
    for (char c : prefix.toCharArray()) {
        int index = c - 'a';
        if (node.children[index] == null) return results;
        node = node.children[index];
    }
    collectWords(node, prefix, results);
    return results;
}

private void collectWords(TrieNode node, String current, List<String> results) {
    if (node == null) return;
    if (node.isEndOfWord) results.add(current);
    for (int i = 0; i < 26; i++) {
        if (node.children[i] != null) {
            collectWords(node.children[i], current + (char)('a' + i), results);
        }
    }
}

Word Dictionary (Search with Dots)

class WordDictionary {
    TrieNode root;

    public WordDictionary() {
        root = new TrieNode();
    }

    public void addWord(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEndOfWord = true;
    }

    public boolean search(String word) {
        return searchHelper(root, word, 0);
    }

    private boolean searchHelper(TrieNode node, String word, int index) {
        if (node == null) return false;
        if (index == word.length()) return node.isEndOfWord;

        char c = word.charAt(index);
        if (c == '.') {
            for (TrieNode child : node.children) {
                if (searchHelper(child, word, index + 1)) return true;
            }
            return false;
        } else {
            return searchHelper(node.children[c - 'a'], word, index + 1);
        }
    }
}

Longest Common Prefix

public String longestCommonPrefix(String[] strs) {
    if (strs.length == 0) return "";
    TrieNode root = new TrieNode();
    for (String s : strs) insert(root, s);

    String prefix = "";
    TrieNode node = root;
    while (hasSingleChild(node)) {
        for (int i = 0; i < 26; i++) {
            if (node.children[i] != null) {
                prefix += (char)('a' + i);
                node = node.children[i];
                break;
            }
        }
    }
    return prefix;
}

private boolean hasSingleChild(TrieNode node) {
    int count = 0;
    for (TrieNode child : node.children) {
        if (child != null) count++;
    }
    return count == 1 && !node.isEndOfWord;
}

When to Use Trie

  • Prefix-based problems: autocomplete, prefix matching
  • Word games: Scrabble, Boggle, word search
  • String matching: find all words matching pattern
  • IP routing: longest prefix match

Trie vs HashMap

Feature Trie HashMap
Prefix search O(m) native O(n × m)
Space Shared prefixes Full strings
Insert/Search O(m) O(m) average
Range queries Efficient Not supported

Practice Problems

0/2solved
Implement Trie (Prefix Tree)
Trie Design

Implement a trie with insert, search, and startsWith methods.

Example:

Input: Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); trie.startsWith("app");

Output: true, true

Standard trie operations.

Optimal Solution — O(m) time, O(N × m) space

Standard trie with array of children

class Trie {
    TrieNode root;
    class TrieNode { TrieNode[] children = new TrieNode[26]; boolean isEnd = false; }
    public Trie() { root = new TrieNode(); }
    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            int i = c - 'a';
            if (node.children[i] == null) node.children[i] = new TrieNode();
            node = node.children[i];
        }
        node.isEnd = true;
    }
    public boolean search(String word) {
        TrieNode node = find(word);
        return node != null && node.isEnd;
    }
    public boolean startsWith(String prefix) { return find(prefix) != null; }
    private TrieNode find(String s) {
        TrieNode node = root;
        for (char c : s.toCharArray()) {
            int i = c - 'a';
            if (node.children[i] == null) return null;
            node = node.children[i];
        }
        return node;
    }
}

Edge Cases:

  • Empty string
  • Prefix is also a word
  • All same prefix
Word Search II
Trie + Backtracking

Given an m x n board of characters and a list of words, return all words on the board. Each word must be constructed from letters of sequentially adjacent cells.

Example:

Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]

Output: ["eat","oath"]

Found words 'eat' and 'oath' on the board.

Optimal Solution — O(M × N × 4^L) where L = max word length time, O(K × L) for trie where K = words space

Build trie from words, then DFS on board using trie for efficient pruning

class Solution {
    public List<String> findWords(char[][] board, String[] words) {
        TrieNode root = buildTrie(words);
        List<String> result = new ArrayList<>();
        for (int i = 0; i < board.length; i++)
            for (int j = 0; j < board[0].length; j++)
                dfs(board, root, i, j, result);
        return result;
    }
    
    private void dfs(char[][] board, TrieNode node, int r, int c, List<String> result) {
        if (r < 0 || r >= board.length || c < 0 || c >= board[0].length) return;
        char ch = board[r][c];
        if (ch == '#' || node.children[ch - 'a'] == null) return;
        node = node.children[ch - 'a'];
        if (node.word != null) {
            result.add(node.word);
            node.word = null;
        }
        board[r][c] = '#';
        dfs(board, node, r+1, c, result);
        dfs(board, node, r-1, c, result);
        dfs(board, node, r, c+1, result);
        dfs(board, node, r, c-1, result);
        board[r][c] = ch;
    }
    
    class TrieNode { TrieNode[] children = new TrieNode[26]; String word; }
    private TrieNode buildTrie(String[] words) {
        TrieNode root = new TrieNode();
        for (String w : words) {
            TrieNode node = root;
            for (char c : w.toCharArray()) {
                int i = c - 'a';
                if (node.children[i] == null) node.children[i] = new TrieNode();
                node = node.children[i];
            }
            node.word = w;
        }
        return root;
    }
}

Edge Cases:

  • No words found
  • All cells used for one word
  • Multiple words share path

Quiz

1. What is the time complexity of inserting a word of length m into a trie?

Question 1 options

2. Why is a trie more space-efficient than storing full strings in a hash set?

Question 2 options

3. What is the primary purpose of Trie (Prefix Tree)?

Question 3 options

4. What is a common mistake when implementing Trie (Prefix Tree)?

Question 4 options

Flashcards

Question

What is a trie and when should you use it?

Answer

A trie (prefix tree) stores strings character-by-character. Use it for prefix-based problems: autocomplete, word matching, longest common prefix. O(m) insert/search where m = word length.

Question

How do you handle '.' (wildcard) in trie search?

Answer

When encountering '.', try all 26 children recursively. Return true if any path matches. This is used in Word Dictionary problems.

Question

What is Trie (Prefix Tree)?

Answer

Trie (Prefix Tree) is a key concept in software engineering.

Question

When to use Trie (Prefix Tree)?

Answer

Use Trie (Prefix Tree) when building production systems that require reliability, scalability, and maintainability.

Question

Trie (Prefix Tree) best practices

Answer

Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.

Revision Notes

Key Takeaways

  • 1.Trie enables O(m) prefix operations where m is word length
  • 2.Each node stores 26 children (for lowercase) and an end-of-word flag
  • 3.Tries excel at prefix-based problems and share common prefixes
  • 4.Wildcard search explores all children at '.' positions

Interview Tips

  • Start with basic insert/search, then add features as needed
  • For wildcard problems, use recursive DFS at '.' positions
  • Mention space optimization: shared prefixes save memory
  • Know when trie is better than hash set (prefix queries vs exact match)

Cheat Sheet

Trie Cheat Sheet

Node Structure:

class TrieNode {
    TrieNode[] children = new TrieNode[26];
    boolean isEndOfWord = false;
}

Core Operations:

  • Insert: O(m) - traverse/create nodes for each character
  • Search: O(m) - traverse nodes, check isEndOfWord
  • startsWith: O(m) - traverse nodes only

Standard Template:

public void insert(String word) {
    TrieNode node = root;
    for (char c : word.toCharArray()) {
        int i = c - 'a';
        if (node.children[i] == null) node.children[i] = new TrieNode();
        node = node.children[i];
    }
    node.isEndOfWord = true;
}

Applications:

  • Autocomplete / Prefix search
  • Word search with wildcards ('.')
  • Longest common prefix
  • Spell checker
  • IP routing (longest prefix match)

Space: O(N × m) where N = words, m = avg length. Shared prefixes save space.

Trie vs HashMap:

  • Trie: native prefix search, shared storage
  • HashMap: O(1) exact lookup, more memory for prefixes