Trie Operations
Table of Contents + β
Say you are building the search box on an app like WhatsApp. A user types βcaβ. You want to instantly show βcatβ, βcarβ, and βcardβ. If you store words in a plain list, you have to scan every single word to find the ones that start with βcaβ. That gets slow fast. A trie fixes this. A trie is a tree where each word is stored letter by letter. So words that share a starting part also share a path.
In this tutorial we look at the one building block that makes all of this work. That building block is the trie node. Then we write the insert operation that adds a word to the trie, walking it letter by letter.
π What Is a Trie Node?
A trie is made of many small pieces called nodes. A trie node is one spot in the tree. It stands for a single character along the way to a word.
Each node carries two things:
- Child links β a way to reach the next characters. Think of it as a small map from a character to the node that comes after it. So from the node after βcβ, a link labelled βaβ takes you to the node for βcaβ.
- An isEndOfWord flag β a true or false value. It tells you whether the path from the root down to this node spells a complete word, not just a part of one.
Now here is something that surprises people. The character itself is not really stored inside the node. The character is the label on the link that got you there. The node just holds the links going out and that one flag.
Let me show you a trie that has the words βcatβ and βcarβ in it.
See how βcatβ and βcarβ share the path c -> a? They only split at the last letter. That sharing is what makes a trie fast. It also saves space.
π― How the Operations Walk the Word
Every trie operation works the same way. You start at the root and you walk one character at a time.
Here is the walk for inserting a word:
- Start at the root node. This is the empty starting point above all words.
- Take the first character of the word. Look in the current nodeβs child links for that character.
- If a link for that character already exists, just step into that child node.
- If there is no link yet, create a fresh node. Attach it under that character. Then step into it.
- Move to the next character and repeat until the word is finished.
- At the last node, set
isEndOfWordto true. This marks that a full word ends right here.
That last step matters a lot. Without the flag, you could not tell βcarβ apart from βcaβ. βcarβ is a real word you added. βcaβ is just a part you walked through on the way.
Tip
A prefix just means the starting part of a word. βcaβ is a prefix of both βcatβ and βcarβ. The whole point of a trie is that shared prefixes share the same path. So you store them once.
π οΈ Steps to Insert a Word
Let me put the insert into clear steps before we write code.
- Create the trie node type. It holds a map of child links and a boolean
isEndOfWordset to false. - Make a root node. The trie always begins from this single empty node.
- To insert a word, set a pointer called
currentto the root. - Loop over each character of the word.
- For the current character, check if
currenthas a child link for it. - If it does not, create a new node and store it under that character.
- Move
currentdown to that child node. - After the loop ends, set
current.isEndOfWordto true.
π» Defining the Node and Inserting a Word
Now the code. We define a trie node, build a small trie, and insert two words that share a prefix. Then we print whether each word was added. We check a word by walking it the same way and reading the flag at the end.
# A trie node: a dict of child links and an end-of-word flag.class TrieNode: def __init__(self): self.children = {} # maps a character to the next TrieNode self.is_end_of_word = False
# Walk the word letter by letter, creating nodes when missing.def insert(root, word): current = root for ch in word: if ch not in current.children: current.children[ch] = TrieNode() current = current.children[ch] current.is_end_of_word = True # mark the full word
# Walk the word and return True only if it ends on an end-of-word node.def search(root, word): current = root for ch in word: if ch not in current.children: return False current = current.children[ch] return current.is_end_of_word
root = TrieNode()insert(root, "cat")insert(root, "car")print("cat added?", "yes" if search(root, "cat") else "no")print("car added?", "yes" if search(root, "car") else "no")print("ca added?", "yes" if search(root, "ca") else "no")The output of the above code will be:
cat added? yescar added? yesca added? noLook at the βca added? noβ line. We did walk through the βcaβ path while adding βcatβ and βcarβ. But we never set the flag on the node for βaβ. So the trie correctly says βcaβ was not added as a word. That is the flag doing its job.
Note
Insert costs O(L) time, where L is the number of characters in the word. You touch one node per character, nothing more. Search costs O(L) too, for the same reason. The number of words already in the trie does not slow you down. That is the whole point.
β±οΈ Complexity at a Glance
Here is the cost of each trie operation. L is the length of the word you are working with.
| Operation | Time | Why |
|---|---|---|
| Insert a word | O(L) | One step per character of the word. |
| Search a word | O(L) | Walk the same path and read the end flag. |
| Create one node | O(1) | Just set up empty links and a false flag. |
Caution
A common mistake is forgetting to set isEndOfWord at the end of insert. Then search walks the path fine but reads a false flag. So it reports the word as missing. Another common slip is reusing a node without checking the link first. That can wipe out words you already added. Always check for the link, and create only when it is missing.
π§© What Youβve Learned
- β
A trie node holds two things: child links to the next characters and an
isEndOfWordflag. - β The character is the label on the link, not a value stored inside the node.
- β Every trie operation walks the word one character at a time, starting from the root.
- β Insert creates a node only when the link for that character is missing, then sets the end flag on the last node.
- β
The
isEndOfWordflag is what separates a real word from a prefix you only passed through. - β Insert and search both run in O(L) time, where L is the length of the word.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What two things does a trie node hold?
Why: A trie node stores links to its child nodes plus a boolean flag marking whether a word ends there.
- 2
Where is the character for a node actually represented?
Why: The character is the label on the child link that leads into the node, not a field inside the node itself.
- 3
During insert, what do you do when no link exists for the current character?
Why: If the link is missing you create a fresh node, attach it under that character, then step into it.
- 4
Why does searching for 'ca' return false after inserting 'cat' and 'car'?
Why: The 'ca' path exists, but its end node was never marked, so isEndOfWord is false and 'ca' is not a stored word.