Prefix Search in Trie
Problem and examples
true if such a prefix exists, otherwise false.
| Inserted Words | Search Prefix | Expected Result | Explanation |
|---|---|---|---|
| ["apple", "banana", "grape"] | "app" | true | The prefix "app" matches the start of the word "apple".Visualization |
| ["apple", "banana", "grape"] | "gr" | true | The prefix "gr" matches the start of the word "grape".Visualization |
| ["apple", "banana", "grape"] | "car" | false | None of the words start with the prefix "car".Visualization |
| [] | "any" | false | No words are inserted into the Trie, so no prefix can be matched.Visualization |
| ["cat", "cater", "car"] | "ca" | true | All words share the prefix "ca".Visualization |
| ["one", "only", "once"] | "" | true | Since every word starts with an empty prefix, we should return true.Visualization |
Explore Prefix Search in Trie
Load an example or enter your own input. Use the player controls to follow each change.
To solve the problem of Prefix Search in Trie, we need to determine if any word stored in the Trie starts with a given prefix. This is different from a full word search because we only care whether a word starts with the prefix — not whether a word matches the prefix entirely.
How we approach the problem
A Trie is a tree-like data structure that is particularly efficient for prefix-based queries. Each node in the Trie represents a character, and a path from the root to any node represents a prefix of some word(s).
When performing a prefix search:
- We start from the root of the Trie.
- We go through each character of the prefix one by one.
- For each character, we check whether it exists as a child of the current node.
- If any character is missing, we conclude that no word with that prefix exists in the Trie.
- If we reach the end of the prefix successfully, it means at least one word in the Trie starts with the prefix.
Example:
Suppose the Trie contains: ["app", "apple", "bat", "ball"]
- Prefix to search:
"ap" - Start at root → Check for 'a' → Exists
- Move to 'a' → Check for 'p' → Exists
- All characters in the prefix found → Return
true
Edge Cases
Case 1 - Empty prefix string
Description: The prefix string is an empty string: ""
- Since every word starts with an empty prefix, we should return
true.
Example:
Trie: ["a", "ab", "abc"]
Prefix: "" → Result: true
Case 2 - Prefix longer than any stored word
Description: The prefix is longer than any word in the Trie.
- Traverse the Trie character by character.
- If at any point the character is not found, return
false.
Example:
Trie: ["do", "dog", "dot"]
Prefix: "dodge" → Result: false
For char 'o', there is no child 'd'. Prefix not present in the trie, hence returning false.
Case 3 - Prefix partially matches a word
Description: The prefix matches the start of a word but does not reach a full word.
- We only care if the prefix matches a path in the Trie.
- If the prefix exists in the Trie path, return
true.
Example:
Trie: ["go", "gone", "golf"]
Prefix: "gol" → Result: true
Case 4 - Prefix not found at all
Description: The prefix has no matching characters in the Trie.
- As soon as we find a character in the prefix that is not in the current node’s children, we return
false.
Example:
Trie: ["cat", "car", "cart"]
Prefix: "dog" → Result: false
Root of the trie has only one child 'c'. First character in given prefix is 'd' and this is not there in the list of children of the root of trie. Therefore, return false.
Case 5 - Multiple words start with the same prefix
Description: The prefix matches the beginning of several words.
- Even if one word starts with the prefix, return
true. - Don’t worry about counting how many words — just whether any word matches.
Example:
Trie: ["star", "start", "stark"]
Prefix: "sta" → Result: true
Case 6 - Trie is empty
Description: No words are present in the Trie at all.
- If Trie is empty, any prefix will return
false.
Example:
Trie: []
Prefix: "a" → Result: false
- Start from the root node of the Trie.
- For each character in the input prefix:
- Check if the character exists in the current node's children map.
- If it does, move to the corresponding child node.
- If it does not, return
false— no word in the Trie starts with this prefix.
- After traversing all characters, return
true— at least one word in the Trie starts with the given prefix.
Implementation and efficiency
C implementation
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define ALPHABET_SIZE 26
typedef struct TrieNode {
struct TrieNode* children[ALPHABET_SIZE];
bool isEndOfWord;
} TrieNode;
TrieNode* createNode() {
TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
node->isEndOfWord = false;
for (int i = 0; i < ALPHABET_SIZE; i++)
node->children[i] = NULL;
return node;
}
void insert(TrieNode* root, const char* word) {
TrieNode* node = root;
for (int i = 0; word[i]; i++) {
int index = word[i] - 'a';
if (!node->children[index])
node->children[index] = createNode();
node = node->children[index];
}
node->isEndOfWord = true;
}
bool startsWith(TrieNode* root, const char* prefix) {
TrieNode* node = root;
for (int i = 0; prefix[i]; i++) {
int index = prefix[i] - 'a';
if (!node->children[index])
return false;
node = node->children[index];
}
return true;
}
int main() {
TrieNode* root = createNode();
insert(root, "apple");
insert(root, "app");
printf("%s\n", startsWith(root, "app") ? "true" : "false");
printf("%s\n", startsWith(root, "apx") ? "true" : "false");
return 0;
}C++ implementation
#include <iostream>
#include <unordered_map>
using namespace std;
class TrieNode {
public:
unordered_map<char, TrieNode*> children;
bool isEndOfWord;
TrieNode() : isEndOfWord(false) {}
};
class Trie {
private:
TrieNode* root;
public:
Trie() { root = new TrieNode(); }
void insert(const string& word) {
TrieNode* node = root;
for (char ch : word) {
if (!node->children.count(ch))
node->children[ch] = new TrieNode();
node = node->children[ch];
}
node->isEndOfWord = true;
}
bool startsWith(const string& prefix) {
TrieNode* node = root;
for (char ch : prefix) {
if (!node->children.count(ch))
return false;
node = node->children[ch];
}
return true;
}
};
int main() {
Trie trie;
trie.insert("apple");
trie.insert("app");
cout << boolalpha << trie.startsWith("app") << endl; // true
cout << trie.startsWith("apx") << endl; // false
return 0;
}Python implementation
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
def startsWith(self, prefix):
node = self.root
for char in prefix:
if char not in node.children:
return False
node = node.children[char]
return True
if __name__ == "__main__":
trie = Trie()
trie.insert("apple")
trie.insert("app")
print(trie.startsWith("app")) # True
print(trie.startsWith("apx")) # FalseJava implementation
import java.util.HashMap;
class TrieNode {
HashMap<Character, TrieNode> children = new HashMap<>();
boolean isEndOfWord = false;
}
public class Trie {
private final TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
node.children.putIfAbsent(ch, new TrieNode());
node = node.children.get(ch);
}
node.isEndOfWord = true;
}
public boolean startsWith(String prefix) {
TrieNode node = root;
for (char ch : prefix.toCharArray()) {
if (!node.children.containsKey(ch)) {
return false;
}
node = node.children.get(ch);
}
return true;
}
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("apple");
trie.insert("app");
System.out.println(trie.startsWith("app")); // true
System.out.println(trie.startsWith("apx")); // false
}
}JavaScript implementation
class TrieNode {
constructor() {
this.children = {};
this.isEndOfWord = false;
}
}
class Trie {
constructor() {
this.root = new TrieNode();
}
insert(word) {
let node = this.root;
for (const char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
}
node.isEndOfWord = true;
}
startsWith(prefix) {
let node = this.root;
for (const char of prefix) {
if (!node.children[char]) {
return false;
}
node = node.children[char];
}
return true;
}
}
// Example usage:
const trie = new Trie();
trie.insert("apple");
trie.insert("app");
console.log(trie.startsWith("app")); // true
console.log(trie.startsWith("apx")); // falseGo implementation
package main
import "fmt"
const alphabetSize = 26
type TrieNode struct {
children [alphabetSize]*TrieNode
isEndOfWord bool
}
type Trie struct {
root *TrieNode
}
func newTrieNode() *TrieNode {
return &TrieNode{}
}
func NewTrie() *Trie {
return &Trie{root: newTrieNode()}
}
func (t *Trie) Insert(word string) {
node := t.root
for _, ch := range word {
index := ch - 'a'
if node.children[index] == nil {
node.children[index] = newTrieNode()
}
node = node.children[index]
}
node.isEndOfWord = true
}
func (t *Trie) StartsWith(prefix string) bool {
node := t.root
for _, ch := range prefix {
index := ch - 'a'
if node.children[index] == nil {
return false
}
node = node.children[index]
}
return true
}
func main() {
trie := NewTrie()
trie.Insert("apple")
trie.Insert("app")
fmt.Println(trie.StartsWith("app")) // true
fmt.Println(trie.StartsWith("apx")) // false
}Rust implementation
use std::collections::HashMap;
struct TrieNode {
children: HashMap<char, TrieNode>,
is_end_of_word: bool,
}
impl TrieNode {
fn new() -> Self {
TrieNode {
children: HashMap::new(),
is_end_of_word: false,
}
}
}
struct Trie {
root: TrieNode,
}
impl Trie {
fn new() -> Self {
Trie { root: TrieNode::new() }
}
fn insert(&mut self, word: &str) {
let mut node = &mut self.root;
for ch in word.chars() {
node = node.children.entry(ch).or_insert_with(TrieNode::new);
}
node.is_end_of_word = true;
}
fn starts_with(&self, prefix: &str) -> bool {
let mut node = &self.root;
for ch in prefix.chars() {
if let Some(next_node) = node.children.get(&ch) {
node = next_node;
} else {
return false;
}
}
true
}
}
fn main() {
let mut trie = Trie::new();
trie.insert("apple");
trie.insert("app");
println!("{}", trie.starts_with("app")); // true
println!("{}", trie.starts_with("apx")); // false
}Kotlin implementation
class TrieNode {
val children = mutableMapOf<Char, TrieNode>()
var isEndOfWord = false
}
class Trie {
private val root = TrieNode()
fun insert(word: String) {
var node = root
for (ch in word) {
node = node.children.getOrPut(ch) { TrieNode() }
}
node.isEndOfWord = true
}
fun startsWith(prefix: String): Boolean {
var node = root
for (ch in prefix) {
node = node.children[ch] ?: return false
}
return true
}
}
fun main() {
val trie = Trie()
trie.insert("apple")
trie.insert("app")
println(trie.startsWith("app")) // true
println(trie.startsWith("apx")) // false
}Swift implementation
class TrieNode {
var children: [Character: TrieNode] = [:]
var isEndOfWord = false
}
class Trie {
private let root = TrieNode()
func insert(_ word: String) {
var node = root
for ch in word {
if node.children[ch] == nil {
node.children[ch] = TrieNode()
}
node = node.children[ch]!
}
node.isEndOfWord = true
}
func startsWith(_ prefix: String) -> Bool {
var node = root
for ch in prefix {
guard let nextNode = node.children[ch] else {
return false
}
node = nextNode
}
return true
}
}
let trie = Trie()
trie.insert("apple")
trie.insert("app")
print(trie.startsWith("app")) // true
print(trie.startsWith("apx")) // falseTypeScript implementation
class TrieNode {
children: { [key: string]: TrieNode } = {};
isEndOfWord: boolean = false;
}
class Trie {
root: TrieNode = new TrieNode();
insert(word: string): void {
let node = this.root;
for (const char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
}
node.isEndOfWord = true;
}
startsWith(prefix: string): boolean {
let node = this.root;
for (const char of prefix) {
if (!node.children[char]) {
return false;
}
node = node.children[char];
}
return true;
}
}
// Example usage
const trie = new Trie();
trie.insert("apple");
trie.insert("app");
console.log(trie.startsWith("app")); // true
console.log(trie.startsWith("apx")); // falseTime complexity
| Case | Time | Explanation |
|---|---|---|
| best case | O(m) | In the best case, all characters of the prefix are found without branching into deeper nodes. The algorithm performs a single traversal of m characters (length of the prefix). |
| average case | O(m) | Regardless of Trie shape or number of words, the algorithm checks each of the m characters once while traversing the prefix path. |
| worst case | O(m) | Even in the worst case, the algorithm must examine each of the m characters in the prefix sequentially from the root node. No backtracking or revisits occur. |
Space complexity
O(1)
The search uses a constant amount of extra space (just a pointer to traverse the Trie), as no additional data structures are created during traversal.