How Does a Word Enter the Trie?

You've seen what a trie looks like after words have been inserted. Now it's time to understand the mechanics of getting a word in there.

Your trie currently holds one word: ten. The path t → e → n exists, with isEnd = true on the n node. Now you need to add tea.

Think about what should happen. The first two characters — t and e — already have nodes in the trie. Creating new nodes for them would break the entire prefix-sharing principle. So the insert must be smart enough to walk existing structure before it starts building new structure.

But how does it know where walking ends and building begins? And once the path is complete, what prevents tea from being a ghost word — a path that exists but isn't marked as a real entry?

There's a clean two-phase pattern here: reuse what exists, create what doesn't, mark the end. You're about to discover it by doing the insert yourself, one character at a time.

Here's the code you're about to enact. Step through each line — notice the check-or-create decision that happens at every character.

1
function insert(word: string) {
2
  let node = this.root
3
  for (const ch of word) {
4
    if (!node.children.has(ch)) {
5
      node.children.set(ch, new TrieNode())
6
    }
7
    node = node.children.get(ch)!
8
  }
9
  node.isEnd = true
10
}

Start at the root — the entry point for every insertion.

1 / 6

Be the Insert Function

The trie already holds one word: ten. The path t → e → n is in place, with an isEnd marker on the final node. Now you need to insert tea.

Think about what the insert function faces at each step. It arrives at a node and needs to follow the next character in the word. But it doesn't know in advance whether that child already exists — it has to check. If the child is there, it steps into it for free. If it isn't, the function creates a new node, links it, and then steps into it. That check-or-create decision happens at every single character, mechanically, without any lookahead or planning.

The trie below is waiting, but the children ahead are hidden behind fog. At each node, before you can proceed, you need to predict: does the next character's node already exist, or do you need to create it? This is exactly the decision the insert function makes at every step of the loop. When the fog lifts, you'll see whether your prediction was right.

The interesting moment will come when the path for tea diverges from the path for ten. The first two characters are shared — t and e already have nodes. But at the third character, tea needs an a-child where ten has an n-child. That's the branching point where the trie grows a new arm, and it happens automatically from the same check-or-create logic. No special branching code. No tree-rebalancing. Just the same one-line decision applied at every depth.

insert("tea")

The trie holds ten. If you add tea, how many NEW nodes do you need to create?

The Pattern You Just Felt

Did you notice the rhythm? Every character in the word triggers the same two-part question: “Does this child exist? If yes, step into it. If no, create it.” Then move to the next character. When the loop ends, stamp isEnd = true on the final node.

That's the entire insert function. No searching for the right position. No rebalancing like a BST. No rehashing like a hash table that's grown too full. Just a straight walk of length L, creating nodes only where the path doesn't exist yet.

The elegance is in what doesn't happen. When you inserted tea into a trie already holding ten, the t and e nodes were reused automatically — not because of any special sharing logic, but because the loop checked “does this child exist?” and the answer was yes. Prefix compression isn't an optimization bolted onto the trie. It's a natural consequence of how insertion works.

The cost is always O(L) where L is the word length. And every word that shares a prefix with existing words is cheaper than a word that doesn't — because it creates fewer new nodes. The more related your data, the more efficient the trie becomes. That's unusual. Most data structures don't get better as you add similar items.

Now that you've felt the pattern, tap any line in the final code to see how it maps to what you just did.

1
function insert(word: string) {
2
  let node = this.root
3
  for (const ch of word) {
4
    if (!node.children.has(ch)) {
5
      node.children.set(ch, new TrieNode())
6
    }
7
    node = node.children.get(ch)!
8
  }
9
  node.isEnd = true
10
}

Tap a line number above to see what it does.