Insert and Search in a Trie

So you built a trie. But a tree just sitting there does nothing useful. You need to put words into it and pull answers back out. That is what the three main trie operations do. In this tutorial we will write insert, search, and startsWith step by step. Then we will run them in five languages.

Think of a phone contact list. You type a few letters and it shows every name that starts with those letters. That is exactly what a trie does. A trie is a tree that stores words one character at a time along a path.

🎯 The Three Operations

A trie really only needs three things you can ask it to do. Here is the short version first.

  • insert(word) adds a word into the trie. It walks one character at a time and builds any path that is missing. Then it marks the last node as the end of a word.
  • search(word) checks if the full word is stored. It walks the path. At the end it checks one flag that says β€œa word ended here”.
  • startsWith(prefix) checks if any word begins with the given letters. It just walks the path and reports whether the path exists. It does not care about the end flag.

The gap between search and startsWith is the part students mix up the most. So let us be clear about it.

Note

search("car") returns true only if β€œcar” was inserted as a complete word. startsWith("car") returns true if β€œcar” sits along the path to any word, like β€œcard” or β€œcart”, even when β€œcar” itself was never inserted. Search needs the end flag. Prefix matching does not.

🧱 What One Node Holds

Before the operations, picture a single node. Each node holds links to its children plus a flag.

  • A map (or array) of children. There is one slot per possible next character.
  • A boolean isEnd that is true when some word finishes right at this node.

This little diagram shows the words β€œcar”, β€œcard”, and β€œcat” living together in one trie.

root

c

a

r (end)

t (end)

d (end)

See how β€œcar” and β€œcard” share the same path up to β€œr”? The node at β€œr” has its end flag on. The node β€œd” hangs off it with its own end flag. That sharing is why a trie saves space.

πŸͺœ Steps to Insert a Word

Insert builds the path. Here is the order it follows.

  1. Start at the root node.
  2. Take the first character of the word.
  3. If the current node has no child for that character, create a new empty node for it.
  4. Move down into that child node.
  5. Repeat for every remaining character.
  6. After the last character, set isEnd to true on the node you landed on.

πŸ”Ž Steps to Search a Word

Search walks the same path. But it creates nothing.

  1. Start at the root node.
  2. For each character, look for a matching child.
  3. If a character has no matching child, the word is not here, so return false.
  4. After the last character, return the isEnd flag of the node you landed on.

πŸ”  Steps for startsWith

Prefix matching is search without the final flag check.

  1. Start at the root node.
  2. For each character of the prefix, look for a matching child.
  3. If any character has no matching child, return false.
  4. If you reach the end of the prefix, return true. You do not check isEnd.

πŸ’» The Code

Now let us put all three together. We insert a few words. Then we search for hits and misses. Then we run a prefix check. Watch the output to see how search and startsWith differ.

trie_ops.py
class Node:
# One trie node: a dict of children and an end flag
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = Node()
# Walk the word and build any missing path, then mark the end
def insert(self, word):
cur = self.root
for ch in word:
if ch not in cur.children:
cur.children[ch] = Node()
cur = cur.children[ch]
cur.is_end = True
# Walk the path; return the end flag of the last node
def search(self, word):
cur = self.root
for ch in word:
if ch not in cur.children:
return False
cur = cur.children[ch]
return cur.is_end
# Walk the path; true if it exists, ignore the end flag
def starts_with(self, prefix):
cur = self.root
for ch in prefix:
if ch not in cur.children:
return False
cur = cur.children[ch]
return True
trie = Trie()
trie.insert("car")
trie.insert("card")
trie.insert("cat")
print("search car :", trie.search("car"))
print("search care :", trie.search("care"))
print("startsWith ca :", trie.starts_with("ca"))
print("startsWith do :", trie.starts_with("do"))

The output of the above code will be:

search car : True
search care : False
startsWith ca : True
startsWith do : False

Read that output slowly. search("care") is false because we never inserted β€œcare” as a whole word. But startsWith("ca") is true because β€œcar”, β€œcard”, and β€œcat” all start with β€œca”. That is the exact gap between the two operations.

⏱️ How Fast Is It?

All three operations have the same cost, and it is easy to reason about. Each one walks the word one character at a time. It does a tiny constant amount of work per character.

Tip

Every operation runs in O(L) time, where L is the length of the word or prefix. The number of words already in the trie does not slow it down at all. A trie holding a million words searches a five-letter word in the same time as a trie holding ten words.

Here is the cost of each operation side by side.

Operation Time What L means
insert(word) O(L) length of the word
search(word) O(L) length of the word
startsWith(prefix) O(L) length of the prefix

Caution

A common mistake is making search return true the moment the path exists. That is wrong. The path for β€œcar” exists even when only β€œcard” was inserted. Search must check the isEnd flag at the final node. Only startsWith is allowed to skip that check.

🧩 What You’ve Learned

  • βœ… A trie supports three core operations: insert, search, and startsWith.
  • βœ… insert walks the word, creates any missing nodes, and marks the last node with an end flag.
  • βœ… search walks the path and returns the end flag, so it is true only for complete inserted words.
  • βœ… startsWith walks the path and returns true if it exists, ignoring the end flag, which is how autocomplete works.
  • βœ… All three run in O(L) time, where L is the length of the word or prefix, no matter how many words the trie holds.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does insert do after walking to the last character of a word?

    Why: Insert builds the path and then marks the final node's isEnd flag so a later search knows a word ends there.

  2. 2

    You inserted only "card". What does search("car") return?

    Why: The path exists, but the isEnd flag at "r" is false, so search correctly returns false.

  3. 3

    What is the key difference between search and startsWith?

    Why: Both walk the same path, but only search checks the end flag; startsWith just confirms the path exists.

  4. 4

    What is the time complexity of searching a word of length L in a trie?

    Why: Search walks one character at a time, so it costs O(L) and does not depend on how many words are stored.

πŸš€ What’s Next?

Share & Connect