Bird
Raised Fist0
DSA Typescriptprogramming~15 mins

Prefix Search Using Trie in DSA Typescript - Deep Dive

Choose your learning style10 modes available
Overview - Prefix Search Using Trie
What is it?
A Trie is a special tree used to store words so we can quickly find all words that start with a given prefix. Prefix search means finding all words in the Trie that begin with some letters you type. This helps in autocomplete features and searching dictionaries fast. It organizes words by their letters, sharing common beginnings to save time and space.
Why it matters
Without prefix search using a Trie, searching for words starting with certain letters would be slow because you'd have to check every word one by one. This would make typing suggestions or searching large word lists frustratingly slow. Tries make these searches very fast and efficient, improving user experience in apps like search engines, phone keyboards, and spell checkers.
Where it fits
Before learning prefix search with Trie, you should understand basic trees and arrays. After this, you can explore advanced string algorithms like suffix trees or tries with additional features like deletion or frequency counts.
Mental Model
Core Idea
A Trie stores words by branching at each letter so that all words sharing a prefix share the same path, making prefix searches fast and easy.
Think of it like...
Imagine a filing cabinet where each drawer is labeled with a letter. To find all files starting with 'ca', you open the 'c' drawer, then inside it the 'a' drawer, and all files inside are your matches. You don't have to look through every drawer, just the path for your prefix.
Root
├─ c
│  ├─ a
│  │  ├─ t (word end)
│  │  └─ r (word end)
│  └─ o
│     └─ w (word end)
└─ d
   └─ o
      └─ g (word end)
Build-Up - 6 Steps
1
FoundationUnderstanding Trie Node Structure
🤔
Concept: Learn what a Trie node is and how it stores children letters and word endings.
A Trie node holds links to child nodes for each letter and a flag to mark if a word ends here. For example, a node for 'c' might have children for 'a' and 'o'. This structure lets us build words letter by letter.
Result
You can represent each letter as a node with pointers to next letters and know when a word finishes.
Understanding the node structure is key because it forms the building block for storing and searching words efficiently.
2
FoundationInserting Words into a Trie
🤔
Concept: Learn how to add words letter by letter into the Trie.
Start at the root node. For each letter in the word, check if a child node exists. If not, create it. Move to that child and repeat for the next letter. At the last letter, mark the node as a word end.
Result
Words like 'cat' and 'car' share the 'c' and 'a' nodes but differ at the last letter nodes.
Knowing how insertion works shows how Tries save space by sharing prefixes and prepare the structure for fast prefix searches.
3
IntermediateSearching for a Prefix in a Trie
🤔Before reading on: do you think searching a prefix requires checking all words or just following nodes? Commit to your answer.
Concept: Learn how to find the node representing the last letter of a prefix.
Start at the root. For each letter in the prefix, move to the child node matching that letter. If at any point the child doesn't exist, the prefix is not found. If all letters are found, you reach the prefix node.
Result
You get the node where the prefix ends, which leads to all words starting with that prefix.
Understanding that prefix search is just following nodes avoids unnecessary checks and makes searches very fast.
4
IntermediateCollecting All Words from a Prefix Node
🤔Before reading on: do you think collecting words from a prefix node is done by scanning all nodes or by exploring only relevant branches? Commit to your answer.
Concept: Learn how to gather all words that start with a prefix by exploring the Trie from the prefix node.
From the prefix node, perform a depth-first search (DFS) or recursion. Each time you find a node marked as a word end, record the word formed by letters along the path. Continue until all branches are explored.
Result
You get a list of all words starting with the prefix, like ['cat', 'car'] for prefix 'ca'.
Knowing how to collect words from the prefix node enables building autocomplete and search suggestion features.
5
AdvancedImplementing Prefix Search in TypeScript
🤔Before reading on: do you think the prefix search function returns nodes or full words? Commit to your answer.
Concept: Combine insertion, prefix search, and word collection into a working TypeScript implementation.
Define a TrieNode class with children map and end flag. Define Trie class with insert and prefixSearch methods. prefixSearch finds the prefix node, then collects all words below it using recursion.
Result
Calling prefixSearch('ca') returns ['cat', 'car'] if those words were inserted.
Seeing the full code connects theory to practice and shows how to build real prefix search features.
6
ExpertOptimizing Trie for Memory and Speed
🤔Before reading on: do you think storing children as arrays or maps is better for all cases? Commit to your answer.
Concept: Explore trade-offs in storing children nodes and techniques to optimize Tries.
Using arrays for children is fast but wastes space if alphabet is large and sparse. Using maps saves space but is slower. Compressed Tries merge chains of single-child nodes to reduce depth. Also, storing counts can help with frequency-based suggestions.
Result
Optimized Tries use less memory and respond faster in real applications.
Understanding these trade-offs helps build efficient systems that scale well with large datasets.
Under the Hood
A Trie works by creating a tree where each node represents a letter. Each path from the root to a node spells out a prefix. Nodes store pointers to child nodes for next letters and a flag if a word ends there. Searching a prefix means following these pointers letter by letter. Collecting words uses recursion to explore all child nodes from the prefix node.
Why designed this way?
Tries were designed to speed up prefix searches by avoiding repeated scanning of all words. Unlike lists or hash tables, Tries organize data by letter paths, sharing common prefixes to save space and time. Alternatives like hash maps can't efficiently find all words starting with a prefix, so Tries fill this gap.
Root
│
├─ c
│  ├─ a
│  │  ├─ t (word end)
│  │  └─ r (word end)
│  └─ o
│     └─ w (word end)
└─ d
   └─ o
      └─ g (word end)
Myth Busters - 3 Common Misconceptions
Quick: Does a Trie store whole words at each node or just letters? Commit to your answer.
Common Belief:A Trie stores whole words at each node for quick access.
Tap to reveal reality
Reality:A Trie stores only single letters at each node, building words by paths from root to nodes.
Why it matters:Believing whole words are stored wastes memory and misunderstands how prefix search works, leading to inefficient designs.
Quick: Is prefix search in a Trie slower than hash map lookup? Commit to your answer.
Common Belief:Hash maps are always faster than Tries for prefix search.
Tap to reveal reality
Reality:Hash maps are fast for exact matches but cannot efficiently find all keys starting with a prefix. Tries excel at prefix search by design.
Why it matters:Using hash maps for prefix search leads to slow, complex code and poor user experience in autocomplete.
Quick: Does a Trie automatically compress paths to save space? Commit to your answer.
Common Belief:All Tries compress paths automatically to save memory.
Tap to reveal reality
Reality:Basic Tries do not compress paths; compressed Tries or Radix Trees do this explicitly.
Why it matters:Assuming automatic compression can cause unexpected high memory use and performance issues.
Expert Zone
1
Trie nodes can store counts of words passing through to support frequency-based suggestions.
2
Compressed Tries reduce depth by merging single-child nodes, improving speed and memory but complicating implementation.
3
Using Unicode or large alphabets requires careful child storage choices to balance speed and memory.
When NOT to use
Tries are not ideal when memory is very limited or when only exact word lookups are needed; hash maps or balanced trees may be better. For very large alphabets or strings, suffix trees or arrays might be more efficient.
Production Patterns
In production, Tries power autocomplete in search engines, spell checkers, and IP routing tables. They often combine with caching and frequency data to prioritize suggestions.
Connections
Hash Tables
Tries and hash tables both store sets of strings but differ in search capabilities.
Understanding hash tables clarifies why Tries are chosen for prefix searches since hash tables can't efficiently find all keys with a common start.
Radix Trees
Radix trees are compressed versions of Tries that merge chains of single-child nodes.
Knowing Radix trees helps understand Trie optimizations for memory and speed in real systems.
Library Cataloging Systems
Both organize items by hierarchical categories or prefixes for fast lookup.
Seeing how libraries group books by subject codes helps grasp how Tries group words by letter prefixes.
Common Pitfalls
#1Trying to store all words at each node instead of just letters.
Wrong approach:class TrieNode { words: string[]; // stores all words passing here children: Map; } // This wastes memory and slows down insertion.
Correct approach:class TrieNode { isWordEnd: boolean; children: Map; } // Store only letters and mark word ends.
Root cause:Misunderstanding that Tries build words by paths, not by storing full words at nodes.
#2Using arrays for children with large alphabets causing wasted space.
Wrong approach:children: TrieNode[] = new Array(65536); // for Unicode letters // Most entries are empty, wasting memory.
Correct approach:children: Map = new Map(); // Only store existing children, saving memory.
Root cause:Not considering alphabet size and sparseness when choosing data structure for children.
#3Stopping prefix search early without checking all letters.
Wrong approach:function prefixSearch(prefix) { if (!root.children.has(prefix[0])) return []; return collectWords(root.children.get(prefix[0])); } // This ignores letters after the first.
Correct approach:function prefixSearch(prefix) { let node = root; for (const letter of prefix) { if (!node.children.has(letter)) return []; node = node.children.get(letter); } return collectWords(node); } // Checks all letters in prefix.
Root cause:Confusing prefix search with single-letter lookup.
Key Takeaways
A Trie stores words letter by letter in a tree structure, sharing prefixes to save space and speed up searches.
Prefix search in a Trie means following nodes for each letter of the prefix, then collecting all words below that node.
Tries are more efficient than hash maps for prefix searches because they organize data by letter paths, not exact keys.
Optimizing Tries involves trade-offs in memory and speed, such as using maps vs arrays or compressing nodes.
Understanding Tries deeply helps build fast autocomplete, spell checkers, and other text-based search features.

Practice

(1/5)
1. What is the main purpose of using a Trie data structure in prefix search?
easy
A. To efficiently find all words that start with a given prefix
B. To sort words alphabetically
C. To store words in reverse order
D. To count the total number of words

Solution

  1. Step 1: Understand Trie usage

    A Trie stores words by their prefixes, allowing quick prefix-based lookups.
  2. Step 2: Identify prefix search goal

    Prefix search means finding all words starting with a given prefix efficiently.
  3. Final Answer:

    To efficiently find all words that start with a given prefix -> Option A
  4. Quick Check:

    Trie = Prefix search [OK]
Hint: Trie is built for prefix-based fast word retrieval [OK]
Common Mistakes:
  • Confusing Trie with sorting structure
  • Thinking Trie stores words reversed
  • Assuming Trie counts total words only
2. Which of the following is the correct TypeScript syntax to define a Trie node with children as a Map and an endOfWord boolean?
easy
A. class TrieNode { children: object; endOfWord: string; constructor() { this.children = {}; this.endOfWord = ''; } }
B. class TrieNode { children: string[]; endOfWord: boolean; constructor() { this.children = []; this.endOfWord = false; } }
C. class TrieNode { children: Map; endOfWord: boolean; constructor() { this.children = new Map(); this.endOfWord = false; } }
D. class TrieNode { children: Map; endOfWord: boolean; constructor() { this.children = new Map(); this.endOfWord = false; } }

Solution

  1. Step 1: Check children type

    Children should be a Map with string keys and TrieNode values for prefix characters.
  2. Step 2: Check endOfWord type

    endOfWord is a boolean indicating if node ends a word.
  3. Final Answer:

    class TrieNode { children: Map; endOfWord: boolean; constructor() { this.children = new Map(); this.endOfWord = false; } } -> Option D
  4. Quick Check:

    Map + boolean endOfWord = class TrieNode { children: Map; endOfWord: boolean; constructor() { this.children = new Map(); this.endOfWord = false; } } [OK]
Hint: Use Map for children and boolean for endOfWord [OK]
Common Mistakes:
  • Using array instead of Map for children
  • Using wrong key type in Map
  • Setting endOfWord as string instead of boolean
3. Given the following TypeScript code snippet, what will be the output of console.log(trie.startsWith('app'))?
class TrieNode {
  children = new Map();
  endOfWord = false;
}

class Trie {
  root = new TrieNode();

  insert(word: string) {
    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.endOfWord = true;
  }

  startsWith(prefix: string): boolean {
    let node = this.root;
    for (const char of prefix) {
      if (!node.children.has(char)) return false;
      node = node.children.get(char)!;
    }
    return true;
  }
}

const trie = new Trie();
trie.insert('apple');
trie.insert('app');
trie.insert('bat');
medium
A. true
B. false
C. undefined
D. Error at runtime

Solution

  1. Step 1: Insert words into Trie

    Words 'apple', 'app', and 'bat' are inserted, so prefix 'app' exists in Trie.
  2. Step 2: Check startsWith method

    Method returns true if prefix characters exist in Trie nodes, which they do for 'app'.
  3. Final Answer:

    true -> Option A
  4. Quick Check:

    Prefix 'app' found = true [OK]
Hint: If prefix inserted, startsWith returns true [OK]
Common Mistakes:
  • Confusing startsWith with search for whole word
  • Expecting false if prefix is a word itself
  • Thinking startsWith returns endOfWord status
4. The following TypeScript code tries to implement a startsWith method for a Trie but always returns false. What is the bug?
startsWith(prefix: string): boolean {
  let node = this.root;
  for (const char of prefix) {
    if (!node.children.has(char)) return false;
    node = node.children.get(char);
  }
  return false;
}
medium
A. It should check endOfWord before returning
B. It should use a for loop with index instead of for-of
C. It should return true after the loop if prefix exists
D. It should initialize node with a new TrieNode

Solution

  1. Step 1: Analyze loop behavior

    The loop correctly traverses nodes for each prefix character.
  2. Step 2: Check return statement

    Method returns false always after loop, so never returns true even if prefix found.
  3. Step 3: Fix return logic

    Return true after loop if all prefix chars found.
  4. Final Answer:

    It should return true after the loop if prefix exists -> Option C
  5. Quick Check:

    Return true after loop if prefix found [OK]
Hint: Return true after loop if prefix nodes exist [OK]
Common Mistakes:
  • Returning false at end regardless
  • Checking endOfWord unnecessarily
  • Incorrect node initialization
5. You want to extend a Trie to return all words starting with a given prefix. Which approach correctly implements this in TypeScript? A) Traverse to prefix node, then recursively collect all words below. B) Store all words in an array and filter by prefix each time. C) Use a hash map to map prefixes to words directly. D) Insert reversed words and search suffixes instead.
hard
A. Store all words in an array and filter by prefix each time.
B. Traverse to prefix node, then recursively collect all words below.
C. Use a hash map to map prefixes to words directly.
D. Insert reversed words and search suffixes instead.

Solution

  1. Step 1: Understand prefix search extension

    To get all words with prefix, first find node matching prefix.
  2. Step 2: Collect words recursively

    From prefix node, recursively traverse children to collect all complete words.
  3. Step 3: Evaluate other options

    Filtering array or hash map is less efficient; reversed words search suffixes, not prefixes.
  4. Final Answer:

    Traverse to prefix node, then recursively collect all words below. -> Option B
  5. Quick Check:

    Recursive traversal from prefix node = Traverse to prefix node, then recursively collect all words below. [OK]
Hint: Find prefix node, then DFS to collect words [OK]
Common Mistakes:
  • Filtering all words each time (slow)
  • Using hash map without prefix traversal
  • Confusing prefix with suffix search
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard