Advanced Trees

Trie (Prefix Tree)

What is a Trie (Prefix Tree)?

A Trie (pronounced "try", from re-"trie"-val, and also called a Prefix Tree) stores a set of strings by turning shared prefixes into shared paths through the tree. Unlike a BST, a trie doesn't store a whole key per node: each edge represents a single character, and the string you get by reading the characters from the root down to any node is that node's prefix. A small boolean flag on a node marks whether the prefix ending there is also a complete word that was inserted.

Why Use a Trie?

This "shared paths for shared prefixes" structure is what makes tries so effective for prefix-based operations: "cat" and "car" and "cart" all share the same "ca"/"car" path and only branch where they actually differ. That single property is why tries are the standard structure behind autocomplete and search-as-you-type suggestions, spell-checkers, and IP routing tables (where the longest matching prefix determines the route), all workloads built entirely around "find everything starting with X."

"cat", "car", "cart", and "dog" stored in one trie
cdaortgcdaortg
Character nodeEnd of a word

Both insertion and search walk one character at a time from the root: insertion follows existing edges where they already exist and creates new nodes only where the path doesn't yet exist, then marks the final node as end-of-word. Search does the same walk: if it ever needs an edge that doesn't exist, the word (or prefix) definitely isn't in the trie; if it reaches the end of the word, it still has to check that node's end-of-word flag, because the path existing only proves the string is a prefix of something, not that it was inserted as its own word.

Algorithm Steps

  1. Insertion: start at the root; for each character in the word, follow the matching child edge if it exists, or create a new node for it if it doesn't; after the last character, mark that node as end-of-word
  2. Search: start at the root; for each character, follow the matching child edge; if it's missing at any point, the word isn't in the trie
  3. If every character is matched, the word is in the trie only if the final node's end-of-word flag is set; otherwise it's merely a prefix of other stored words

Time Complexity

  • Time Complexity: Insertion and search both cost O(L), where L is the word's length.
  • Space Complexity: Up to O(total characters) across all inserted words, though shared prefixes reduce this in practice.

Time Complexity Analysis

Both operations run in O(L) time, where L is the length of the word, not O(log n) or O(n) in terms of how many words are already stored. A hash set can also do exact lookups in roughly that time, but it can't efficiently answer "what words start with this prefix" the way a trie can, since a matching prefix's subtree already holds exactly the words that share it.

Insert words to build the trie, then search and watch it follow one letter at a time

Trie is empty
No trie yet, insert a word or generate random words
Character nodeEnd of a wordRoot (empty prefix)Search pathNewly created

Test Your Knowledge before moving forward!

Trie (Prefix Tree) Quiz

How it works:

  • +1 point for each correct answer
  • 0 points for wrong answers
  • -0.5 point penalty for viewing explanations
  • Earn stars based on your final score (max 5 stars)

Trie Implementation

// Trie (Prefix Tree) node
class TrieNode {
  constructor() {
    this.children = {}; // character -> TrieNode
    this.isEnd = false;  // true if a word ends at this node
  }
}

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

  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;
  }

  search(word) {
    const node = this._walk(word);
    return node !== null && node.isEnd;
  }

  startsWith(prefix) {
    return this._walk(prefix) !== null;
  }

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

// Usage example
const trie = new Trie();
["cat", "car", "cart", "dog"].forEach((w) => trie.insert(w));

trie.search("car");       // true
trie.search("ca");        // false — only a prefix, not inserted as a word
trie.startsWith("ca");    // true — "cat"/"car"/"cart" all start with "ca"

Done With the Learning

Mark Trie (Prefix Tree) as done and view it on your dashboard