Recommended resources
The freely available resources this lesson will direct you to.
Welcome back! In our last lesson, we built a strong conceptual foundation for the Trie data structure. We explored its prefix-based structure, the critical role of the isEndOfWord flag, and most importantly, we established when a Trie is a more powerful tool than a hash map—specifically, for any task involving prefix lookups.
Today, we're moving from concept to code. This lesson is all about implementation. You will learn to implement the core insert, search, and startsWith operations for a standard Trie. By the end, you'll have a complete, working Trie class, a fundamental tool for tackling a variety of string-based problems in coding interviews and beyond.
The Building Blocks: TrieNode and Trie
Before we can implement the operations, we need to define the structure of the Trie itself. This involves two main components: the TrieNode class, which represents each character in our tree, and the Trie class, which will orchestrate the operations starting from a root node.
As we discussed, a TrieNode needs two things:
- A way to point to its children (the next characters in a word).
- A boolean flag to mark if the path to this node represents a complete word.
For the children, a common and efficient approach for a known, small character set like lowercase English letters is a fixed-size array. We can use an array of size 26, where index 0 corresponds to 'a', index 1 to 'b', and so on.
Here's a simple Python class definition for the TrieNode:
class TrieNode:
def __init__(self):
# children will store TrieNode objects
self.children = [None] * 26
self.isEndOfWord = False
The main Trie class is even simpler to start. It just needs a root node, which is itself an empty TrieNode. All operations will begin from this root.
class Trie:
def __init__(self):
self.root = TrieNode()
This is a common pattern in tree-based data structures: a simple container class that holds the root and the methods to manipulate the structure.
The insert Operation
Now, let's implement the insert method. The goal is to add a word to our trie, creating new nodes as needed and reusing existing ones if a prefix already exists.
The process is an iterative traversal:
- Start with a
current_nodepointer atself.root. - For each character in the input
word:
a. Calculate the character's index (e.g.,index = ord(char) - ord('a')).
b. Check ifcurrent_node.children[index]isNone. If it is, this path doesn't exist yet, so we create a newTrieNodeat that position.
c. Updatecurrent_nodeto move down the tree:current_node = current_node.children[index]. - After the loop finishes,
current_nodepoints to the node representing the last character of the word. We mark it as a complete word by settingcurrent_node.isEndOfWord = True.
This image perfectly visualizes the insert process. Notice how for "trie", the 't', 'r', and 'e' nodes are added sequentially. When "algo" is added, a new branch is created from the root.

The following video provides a live walkthrough of coding the insert function.
Implement Trie (Prefix Tree) - Leetcode 208
The NeetCode video on implementing a Trie walks through the Python code for the insert method.
Watch the segment from this timestamp. The presenter implements insert by iterating through the word's characters and creating nodes only when they don't already exist. Pay close attention to how the current pointer is updated to traverse down the trie.
The search and startsWith Operations
The logic for search and startsWith is very similar to insert: both involve traversing the trie character by character. The difference lies in what they do after the traversal.
To make our implementation clean and avoid repeating code—a good practice you'll recognize from your internship experience—we can create a helper method. Let's call it _search_prefix. This helper will take a word or prefix as input and traverse the trie. It will return the final node if the path exists, or None if the path is broken at any point.
# Helper method within the Trie class
def _search_prefix(self, word):
current_node = self.root
for char in word:
index = ord(char) - ord('a')
if current_node.children[index] is None:
return None # Path does not exist
current_node = current_node.children[index]
return current_node # Path exists, return the final node
With this helper, search and startsWith become incredibly simple.
search(word)
To search for a complete word, we need two conditions to be met:
- The entire path for the word must exist in the trie.
- The final node on that path must be marked as the end of a word (
isEndOfWord == True).
Using our helper:
def search(self, word: str) -> bool:
final_node = self._search_prefix(word)
# The word exists if the path was found AND it's marked as an end of a word.
return final_node is not None and final_node.isEndOfWord
startsWith(prefix)
To check if any word starts with a given prefix, we only need to satisfy one condition:
- The entire path for the prefix must exist in the trie.
We don't care if the final node is the end of a word or not.
Using our helper:
def startsWith(self, prefix: str) -> bool:
final_node = self._search_prefix(prefix)
# The prefix exists if the path was found.
return final_node is not None
This clear separation of concerns is what makes the trie so elegant. The traversal logic is shared, and the specific operation is determined by the final check.
L1. Implement TRIE | INSERT | SEARCH | STARTSWITH
The "take U forward" channel gives a great visual explanation of the logic behind search and startsWith.
First, watch the explanation of the search functionality from this segment. Notice how the traversal is identical to insertion, but the key is checking the boolean flag at the very end. Then, watch the explanation for startsWith from here, which highlights that you only need to confirm the path exists.
Complete Implementation and Analysis
Let's now assemble all the pieces into a complete Trie class. The following resource provides a clean, well-documented implementation that mirrors the LeetCode problem statement.
208. Implement Trie (Prefix Tree) - In-Depth Explanation
This AlgoMonster article provides a full solution for the "Implement Trie" problem, along with complexity analysis and a discussion of common mistakes.
First, study the full Python implementation. Note how it uses the _search_prefix helper method we discussed to keep the search and startsWith methods clean and concise. Next, read the Time and Space Complexity analysis. For a word or prefix of length L, all three operations (insert, search, startsWith) have a time complexity of O(L), as they involve a single pass. The space complexity is proportional to the total number of characters across all words, benefiting from prefix sharing. Finally, review the section on Common Pitfalls. Understanding these, especially the distinction between search and startsWith logic and the correct way to create nodes, is key to avoiding bugs in your own implementations.
Conclusion
In this lesson, we transitioned from the theory of Tries to their practical implementation. You've now seen how to construct a Trie class from the ground up, complete with its core insert, search, and startsWith methods.
Key Takeaways:
- A
Trieis typically implemented with aTrieNodeclass (for the nodes) and aTrieclass (for the root and methods). - The
insertoperation traverses the trie, creating newTrieNodes for characters not yet in the path, and marks the final node withisEndOfWord = True. - The
searchoperation traverses the trie and returnsTrueonly if the full path exists and the final node'sisEndOfWordflag is true. - The
startsWithoperation traverses the trie and returnsTrueif the full path for the prefix exists, regardless of theisEndOfWordflag. - All three core operations have a time complexity of , where is the length of the string, making them highly efficient.
You now have a solid grasp of how to build and use a standard Trie. This is a significant step, but the real power of a Trie becomes apparent when we combine it with other algorithms.
In the next lesson, we will do just that. We'll revisit backtracking and see how a Trie can be used to dramatically optimize a search for words on a 2D grid, a classic and challenging problem type (e.g., Word Search II).