TechLead
Advanced
20 min
Full Guide

Tries

Trie data structure for efficient string operations

A Trie (prefix tree) is a tree-shaped data structure where each node represents a single character and the path from root to a node spells a prefix. Tries give O(m) insert, search, and prefix-match where m is the word length — far faster than scanning an array of strings for prefix queries.

Core Implementation

class TrieNode {
  constructor() {
    this.children = {};   // char -> TrieNode
    this.isEnd = false;   // marks end of a valid word
  }
}

class Trie {
  constructor() { this.root = new TrieNode(); }

  // O(m)
  insert(word) {
    let node = this.root;
    for (const ch of word) {
      if (!node.children[ch]) node.children[ch] = new TrieNode();
      node = node.children[ch];
    }
    node.isEnd = true;
  }

  // O(m) — returns true if exact word exists
  search(word) {
    const node = this._traverse(word);
    return node !== null && node.isEnd;
  }

  // O(m) — returns true if any word starts with prefix
  startsWith(prefix) {
    return this._traverse(prefix) !== null;
  }

  _traverse(str) {
    let node = this.root;
    for (const ch of str) {
      if (!node.children[ch]) return null;
      node = node.children[ch];
    }
    return node;
  }
}

Autocomplete

Navigate to the prefix node, then collect all words in that subtree using DFS.

Trie.prototype.autocomplete = function(prefix) {
  const node = this._traverse(prefix);
  if (!node) return [];
  const results = [];
  const dfs = (n, current) => {
    if (n.isEnd) results.push(current);
    for (const [ch, child] of Object.entries(n.children)) dfs(child, current + ch);
  };
  dfs(node, prefix);
  return results;
};

const t = new Trie();
['apple', 'app', 'application', 'apt'].forEach(w => t.insert(w));
t.autocomplete('app'); // ['app', 'apple', 'application']

Word Search in a Board

Build a Trie from the word list, then DFS across the board. The Trie lets you prune paths that cannot complete any word early — far faster than checking each word individually.

function findWords(board, words) {
  const trie = new Trie();
  words.forEach(w => trie.insert(w));
  const found = new Set();
  const rows = board.length, cols = board[0].length;

  function dfs(node, r, c, path) {
    if (node.isEnd) found.add(path);
    if (r < 0 || r >= rows || c < 0 || c >= cols || board[r][c] === '#') return;
    const ch = board[r][c];
    const child = node.children[ch];
    if (!child) return;
    board[r][c] = '#'; // mark visited
    for (const [dr, dc] of [[-1,0],[1,0],[0,-1],[0,1]]) dfs(child, r+dr, c+dc, path+ch);
    board[r][c] = ch; // restore
  }

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

  return [...found];
}

Compressed Trie (Radix Tree)

A standard Trie wastes nodes on long chains with no branching. A radix tree compresses each unbranched chain into a single node storing the whole substring. JavaScript's Map-based route matching (e.g., Express, React Router) is implemented with a radix tree under the hood.

Complexity

  • Insert / Search / StartsWith: O(m) time where m = key length
  • Space: O(ALPHABET × total characters) — can be large for broad alphabets
  • Autocomplete: O(m + k) where k = number of matching words returned

Continue Learning