Skip to main content
Data Structures & Algorithms10 min read

Trie Data Structure: Complete Interview Guide

Master the trie (prefix tree) for autocomplete, spell checking, and word search problems. One of the most underrated data structures in interview prep.

What is a Trie?

A trie (pronounced "try") is a tree-like data structure that stores strings character by character. Each node represents a single character, and paths from root to leaf (or marked nodes) represent complete words. The key advantage: prefix lookups are O(m) where m is the prefix length, regardless of how many words are stored.

When to Use a Trie

  • Autocomplete / typeahead: Find all words starting with a prefix
  • Spell checking: Check if a word exists, suggest corrections
  • Word search in grids: Efficiently prune search paths
  • IP routing: Longest prefix matching
  • Dictionary operations: Insert, search, prefix search in O(m) time

If the problem involves prefixes, word lookups, or building words character by character, think trie.

Basic Trie Implementation

class TrieNode {
  children: Map<string, TrieNode> = new Map()
  isEndOfWord: boolean = false
}

class Trie {
  root: TrieNode = new TrieNode()

  // Insert a word - O(m) time, O(m) space
  insert(word: string): void {
    let node = this.root
    for (const char of word) {
      if (!node.children.has(char)) {
        node.children.set(char, new TrieNode())
      }
      node = node.children.get(char)!
    }
    node.isEndOfWord = true
  }

  // Search for exact word - O(m) time
  search(word: string): boolean {
    const node = this.findNode(word)
    return node !== null && node.isEndOfWord
  }

  // Check if any word starts with prefix - O(m) time
  startsWith(prefix: string): boolean {
    return this.findNode(prefix) !== null
  }

  // Helper: traverse to the node for a given string
  private findNode(str: string): TrieNode | null {
    let node = this.root
    for (const char of str) {
      if (!node.children.has(char)) return null
      node = node.children.get(char)!
    }
    return node
  }
}

Pattern 1: Autocomplete / Prefix Search

Problem: Given a dictionary of words and a prefix, return all words that start with that prefix.

function autocomplete(trie: Trie, prefix: string): string[] {
  const results: string[] = []
  const node = trie.findNode(prefix)
  if (!node) return results

  // DFS to collect all words from this node
  function dfs(node: TrieNode, path: string) {
    if (node.isEndOfWord) {
      results.push(path)
    }
    for (const [char, child] of node.children) {
      dfs(child, path + char)
    }
  }

  dfs(node, prefix)
  return results
}

// Usage:
// trie has: ["apple", "app", "application", "banana"]
// autocomplete(trie, "app") → ["app", "apple", "application"]

Pattern 2: Word Search II (LeetCode 212)

Problem: Given a 2D board of characters and a list of words, find all words that can be formed by adjacent cells (up, down, left, right).

This is where tries shine — instead of searching for each word separately (slow), build a trie from all words and search the grid once.

function findWords(board: string[][], words: string[]): string[] {
  const trie = new Trie()
  for (const word of words) trie.insert(word)

  const results = new Set<string>()
  const rows = board.length, cols = board[0].length

  function dfs(r: number, c: number, node: TrieNode, path: string) {
    if (r < 0 || r >= rows || c < 0 || c >= cols) return
    const char = board[r][c]
    if (char === '#' || !node.children.has(char)) return

    const next = node.children.get(char)!
    const newPath = path + char

    if (next.isEndOfWord) {
      results.add(newPath)
      // Don't return — there might be longer words
    }

    board[r][c] = '#' // mark visited
    dfs(r + 1, c, next, newPath)
    dfs(r - 1, c, next, newPath)
    dfs(r, c + 1, next, newPath)
    dfs(r, c - 1, next, newPath)
    board[r][c] = char // restore

    // Optimization: prune empty branches
    if (next.children.size === 0) {
      node.children.delete(char)
    }
  }

  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      dfs(r, c, trie.root, '')
    }
  }

  return [...results]
}

// Key insight: trie lets us prune entire search branches.
// Without trie: O(words * cells * 4^wordLen)
// With trie: O(cells * 4^maxLen) — search all words simultaneously

Pattern 3: Design Add and Search Words (LeetCode 211)

Problem: Support adding words and searching with '.' wildcards, where '.' matches any character.

class WordDictionary {
  root: TrieNode = new TrieNode()

  addWord(word: string): void {
    let node = this.root
    for (const char of word) {
      if (!node.children.has(char)) {
        node.children.set(char, new TrieNode())
      }
      node = node.children.get(char)!
    }
    node.isEndOfWord = true
  }

  search(word: string): boolean {
    return this.dfs(word, 0, this.root)
  }

  private dfs(word: string, index: number, node: TrieNode): boolean {
    if (index === word.length) return node.isEndOfWord

    const char = word[index]

    if (char === '.') {
      // Wildcard: try all children
      for (const child of node.children.values()) {
        if (this.dfs(word, index + 1, child)) return true
      }
      return false
    }

    if (!node.children.has(char)) return false
    return this.dfs(word, index + 1, node.children.get(char)!)
  }
}

// addWord("bad"), addWord("dad"), addWord("mad")
// search(".ad") → true (matches all three)
// search("b..") → true (matches "bad")
// search("b.d") → true (matches "bad")

Trie Complexity Analysis

Insert

O(m)

m = word length

Search

O(m)

m = word length

Space

O(n × m)

n words, m avg length

Optimization: Array vs Map Children

For lowercase English letters only, use a fixed array of 26 instead of a Map. This is faster (array index vs hash lookup) and uses less memory per node.

class TrieNodeArray {
  children: (TrieNodeArray | null)[] = new Array(26).fill(null)
  isEndOfWord: boolean = false
}

// Access: node.children[char.charCodeAt(0) - 97]
// 'a' = index 0, 'b' = index 1, ..., 'z' = index 25
// Faster than Map for known alphabet, but wastes space for sparse nodes

Problems to Practice

  • Implement Trie (LeetCode 208) — Start here. Core insert/search/startsWith
  • Word Search II (LeetCode 212) — Hard. Trie + backtracking on grid
  • Design Search Autocomplete (LeetCode 642) — Trie with frequency ranking
  • Add and Search Word (LeetCode 211) — Trie with wildcard DFS
  • Replace Words (LeetCode 648) — Find shortest prefix root
  • Maximum XOR (LeetCode 421) — Bitwise trie, advanced

Practice Trie Problems on HireReady

Build muscle memory with spaced repetition. Our FSRS algorithm ensures you review trie problems at the optimal intervals for long-term retention.

Start Practicing Free →