Code Templates

This page provides reusable trie implementations that you can adapt for various interview problems. Before diving in, make sure you understand the core concepts from the Concept Guide.

The Basic Trie Template

This template includes the core operations: insert, search, startsWith, and delete. It directly solves LeetCode 208: Implement Trie.

class TrieNode {
    constructor() {
        this.children = new Map();
        this.isEndOfWord = false;
        this.count = 0; // Words passing through this node
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    // Insert word into trie
    insert(word) {
        let node = this.root;
        for (const char of word) {
            if (!node.children.has(char)) {
                node.children.set(char, new TrieNode());
            }
            node = node.children.get(char);
            node.count++;
        }
        node.isEndOfWord = true;
    }

    // Check if word exists
    search(word) {
        const node = this._getNode(word);
        return node !== null && node.isEndOfWord;
    }

    // Check if any word starts with prefix
    startsWith(prefix) {
        return this._getNode(prefix) !== null;
    }

    // Delete word from trie
    delete(word) {
        return this._deleteHelper(this.root, word, 0);
    }

    // Get all words with given prefix
    getWordsWithPrefix(prefix) {
        const node = this._getNode(prefix);
        if (!node) return [];

        const results = [];
        this._collectWords(node, prefix, results);
        return results;
    }

    // Helper: traverse to node for given word
    _getNode(word) {
        let node = this.root;
        for (const char of word) {
            if (!node.children.has(char)) {
                return null;
            }
            node = node.children.get(char);
        }
        return node;
    }

    // Helper: recursive delete
    _deleteHelper(node, word, index) {
        if (index === word.length) {
            if (!node.isEndOfWord) return false;
            node.isEndOfWord = false;
            return node.children.size === 0;
        }

        const char = word[index];
        if (!node.children.has(char)) return false;

        const shouldDelete = this._deleteHelper(
            node.children.get(char), word, index + 1
        );

        if (shouldDelete) {
            node.children.delete(char);
            return node.children.size === 0 && !node.isEndOfWord;
        }
        return false;
    }

    // Helper: collect all words from node
    _collectWords(node, current, results) {
        if (node.isEndOfWord) {
            results.push(current);
        }
        for (const [char, child] of node.children) {
            this._collectWords(child, current + char, results);
        }
    }
}
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False
        self.count = 0  # Words passing through this node

class Trie:
    def __init__(self):
        self.root = TrieNode()

    # Insert word into trie
    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
            node.count += 1
        node.is_end_of_word = True

    # Check if word exists
    def search(self, word: str) -> bool:
        node = self._get_node(word)
        return node is not None and node.is_end_of_word

    # Check if any word starts with prefix
    def starts_with(self, prefix: str) -> bool:
        return self._get_node(prefix) is not None

    # Delete word from trie
    def delete(self, word: str) -> bool:
        return self._delete_helper(self.root, word, 0)

    # Get all words with given prefix
    def get_words_with_prefix(self, prefix: str) -> list:
        node = self._get_node(prefix)
        if not node:
            return []

        results = []
        self._collect_words(node, prefix, results)
        return results

    # Helper: traverse to node for given word
    def _get_node(self, word: str):
        node = self.root
        for char in word:
            if char not in node.children:
                return None
            node = node.children[char]
        return node

    # Helper: recursive delete
    def _delete_helper(self, node, word: str, index: int) -> bool:
        if index == len(word):
            if not node.is_end_of_word:
                return False
            node.is_end_of_word = False
            return len(node.children) == 0

        char = word[index]
        if char not in node.children:
            return False

        should_delete = self._delete_helper(
            node.children[char], word, index + 1
        )

        if should_delete:
            del node.children[char]
            return len(node.children) == 0 and not node.is_end_of_word
        return False

    # Helper: collect all words from node
    def _collect_words(self, node, current: str, results: list):
        if node.is_end_of_word:
            results.append(current)
        for char, child in node.children.items():
            self._collect_words(child, current + char, results)
import java.util.*;

class TrieNode {
    Map<Character, TrieNode> children;
    boolean isEndOfWord;
    int count; // Words passing through this node

    public TrieNode() {
        children = new HashMap<>();
        isEndOfWord = false;
        count = 0;
    }
}

class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // Insert word into trie
    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.count++;
        }
        node.isEndOfWord = true;
    }

    // Check if word exists
    public boolean search(String word) {
        TrieNode node = getNode(word);
        return node != null && node.isEndOfWord;
    }

    // Check if any word starts with prefix
    public boolean startsWith(String prefix) {
        return getNode(prefix) != null;
    }

    // Delete word from trie
    public boolean delete(String word) {
        return deleteHelper(root, word, 0);
    }

    // Get all words with given prefix
    public List<String> getWordsWithPrefix(String prefix) {
        TrieNode node = getNode(prefix);
        if (node == null) return new ArrayList<>();

        List<String> results = new ArrayList<>();
        collectWords(node, prefix, results);
        return results;
    }

    // Helper: traverse to node for given word
    private TrieNode getNode(String word) {
        TrieNode node = root;
        for (char ch : word.toCharArray()) {
            if (!node.children.containsKey(ch)) {
                return null;
            }
            node = node.children.get(ch);
        }
        return node;
    }

    // Helper: recursive delete
    private boolean deleteHelper(TrieNode node, String word, int index) {
        if (index == word.length()) {
            if (!node.isEndOfWord) return false;
            node.isEndOfWord = false;
            return node.children.size() == 0;
        }

        char ch = word.charAt(index);
        if (!node.children.containsKey(ch)) return false;

        boolean shouldDelete = deleteHelper(
            node.children.get(ch), word, index + 1
        );

        if (shouldDelete) {
            node.children.remove(ch);
            return node.children.size() == 0 && !node.isEndOfWord;
        }
        return false;
    }

    // Helper: collect all words from node
    private void collectWords(TrieNode node, String current, List<String> results) {
        if (node.isEndOfWord) {
            results.add(current);
        }
        for (Map.Entry<Character, TrieNode> entry : node.children.entrySet()) {
            collectWords(entry.getValue(), current + entry.getKey(), results);
        }
    }
}
#include <unordered_map>
#include <string>
#include <vector>

class TrieNode {
public:
    std::unordered_map<char, TrieNode*> children;
    bool isEndOfWord;
    int count; // Words passing through this node

    TrieNode() : isEndOfWord(false), count(0) {}
};

class Trie {
private:
    TrieNode* root;

    // Helper: traverse to node for given word
    TrieNode* getNode(const std::string& word) {
        TrieNode* node = root;
        for (char ch : word) {
            if (node->children.find(ch) == node->children.end()) {
                return nullptr;
            }
            node = node->children[ch];
        }
        return node;
    }

    // Helper: recursive delete
    bool deleteHelper(TrieNode* node, const std::string& word, int index) {
        if (index == word.length()) {
            if (!node->isEndOfWord) return false;
            node->isEndOfWord = false;
            return node->children.empty();
        }

        char ch = word[index];
        if (node->children.find(ch) == node->children.end()) {
            return false;
        }

        bool shouldDelete = deleteHelper(node->children[ch], word, index + 1);

        if (shouldDelete) {
            delete node->children[ch];
            node->children.erase(ch);
            return node->children.empty() && !node->isEndOfWord;
        }
        return false;
    }

    // Helper: collect all words from node
    void collectWords(TrieNode* node, std::string current,
                      std::vector<std::string>& results) {
        if (node->isEndOfWord) {
            results.push_back(current);
        }
        for (const auto& pair : node->children) {
            collectWords(pair.second, current + pair.first, results);
        }
    }

public:
    Trie() {
        root = new TrieNode();
    }

    // Insert word into trie
    void insert(const std::string& word) {
        TrieNode* node = root;
        for (char ch : word) {
            if (node->children.find(ch) == node->children.end()) {
                node->children[ch] = new TrieNode();
            }
            node = node->children[ch];
            node->count++;
        }
        node->isEndOfWord = true;
    }

    // Check if word exists
    bool search(const std::string& word) {
        TrieNode* node = getNode(word);
        return node != nullptr && node->isEndOfWord;
    }

    // Check if any word starts with prefix
    bool startsWith(const std::string& prefix) {
        return getNode(prefix) != nullptr;
    }

    // Delete word from trie
    bool deleteWord(const std::string& word) {
        return deleteHelper(root, word, 0);
    }

    // Get all words with given prefix
    std::vector<std::string> getWordsWithPrefix(const std::string& prefix) {
        TrieNode* node = getNode(prefix);
        if (!node) return {};

        std::vector<std::string> results;
        collectWords(node, prefix, results);
        return results;
    }
};
package main

type TrieNode struct {
    children    map[rune]*TrieNode
    isEndOfWord bool
    count       int // Words passing through this node
}

func NewTrieNode() *TrieNode {
    return &TrieNode{
        children:    make(map[rune]*TrieNode),
        isEndOfWord: false,
        count:       0,
    }
}

type Trie struct {
    root *TrieNode
}

func NewTrie() *Trie {
    return &Trie{root: NewTrieNode()}
}

// Insert word into trie
func (t *Trie) Insert(word string) {
    node := t.root
    for _, ch := range word {
        if node.children[ch] == nil {
            node.children[ch] = NewTrieNode()
        }
        node = node.children[ch]
        node.count++
    }
    node.isEndOfWord = true
}

// Check if word exists
func (t *Trie) Search(word string) bool {
    node := t.getNode(word)
    return node != nil && node.isEndOfWord
}

// Check if any word starts with prefix
func (t *Trie) StartsWith(prefix string) bool {
    return t.getNode(prefix) != nil
}

// Delete word from trie
func (t *Trie) Delete(word string) bool {
    return t.deleteHelper(t.root, word, 0)
}

// Get all words with given prefix
func (t *Trie) GetWordsWithPrefix(prefix string) []string {
    node := t.getNode(prefix)
    if node == nil {
        return []string{}
    }

    results := []string{}
    t.collectWords(node, prefix, &results)
    return results
}

// Helper: traverse to node for given word
func (t *Trie) getNode(word string) *TrieNode {
    node := t.root
    for _, ch := range word {
        if node.children[ch] == nil {
            return nil
        }
        node = node.children[ch]
    }
    return node
}

// Helper: recursive delete
func (t *Trie) deleteHelper(node *TrieNode, word string, index int) bool {
    if index == len(word) {
        if !node.isEndOfWord {
            return false
        }
        node.isEndOfWord = false
        return len(node.children) == 0
    }

    ch := rune(word[index])
    if node.children[ch] == nil {
        return false
    }

    shouldDelete := t.deleteHelper(node.children[ch], word, index+1)

    if shouldDelete {
        delete(node.children, ch)
        return len(node.children) == 0 && !node.isEndOfWord
    }
    return false
}

// Helper: collect all words from node
func (t *Trie) collectWords(node *TrieNode, current string, results *[]string) {
    if node.isEndOfWord {
        *results = append(*results, current)
    }
    for ch, child := range node.children {
        t.collectWords(child, current+string(ch), results)
    }
}
class TrieNode
  attr_accessor :children, :is_end_of_word, :count

  def initialize
    @children = {}
    @is_end_of_word = false
    @count = 0 # Words passing through this node
  end
end

class Trie
  def initialize
    @root = TrieNode.new
  end

  # Insert word into trie
  def insert(word)
    node = @root
    word.each_char do |char|
      node.children[char] ||= TrieNode.new
      node = node.children[char]
      node.count += 1
    end
    node.is_end_of_word = true
  end

  # Check if word exists
  def search(word)
    node = get_node(word)
    !node.nil? && node.is_end_of_word
  end

  # Check if any word starts with prefix
  def starts_with(prefix)
    !get_node(prefix).nil?
  end

  # Delete word from trie
  def delete(word)
    delete_helper(@root, word, 0)
  end

  # Get all words with given prefix
  def get_words_with_prefix(prefix)
    node = get_node(prefix)
    return [] if node.nil?

    results = []
    collect_words(node, prefix, results)
    results
  end

  private

  # Helper: traverse to node for given word
  def get_node(word)
    node = @root
    word.each_char do |char|
      return nil unless node.children[char]
      node = node.children[char]
    end
    node
  end

  # Helper: recursive delete
  def delete_helper(node, word, index)
    if index == word.length
      return false unless node.is_end_of_word
      node.is_end_of_word = false
      return node.children.empty?
    end

    char = word[index]
    return false unless node.children[char]

    should_delete = delete_helper(node.children[char], word, index + 1)

    if should_delete
      node.children.delete(char)
      return node.children.empty? && !node.is_end_of_word
    end
    false
  end

  # Helper: collect all words from node
  def collect_words(node, current, results)
    results << current if node.is_end_of_word
    node.children.each do |char, child|
      collect_words(child, current + char, results)
    end
  end
end

Code Breakdown

Key Variables

  • root: The starting node representing an empty prefix
  • children: Map/dictionary storing child nodes keyed by character
  • isEndOfWord: Boolean flag marking complete words
  • count: Optional counter for words passing through (useful for prefix frequency)

How the Template Works

  flowchart TD
    subgraph Insert["Insert Operation"]
        I1[Start at root] --> I2{Character exists?}
        I2 -->|No| I3[Create new node]
        I2 -->|Yes| I4[Move to child]
        I3 --> I4
        I4 --> I5{More chars?}
        I5 -->|Yes| I2
        I5 -->|No| I6[Mark as word end]
    end

    subgraph Search["Search Operation"]
        S1[Start at root] --> S2{Character exists?}
        S2 -->|No| S3[Return false]
        S2 -->|Yes| S4[Move to child]
        S4 --> S5{More chars?}
        S5 -->|Yes| S2
        S5 -->|No| S6{Is word end?}
        S6 -->|Yes| S7[Return true]
        S6 -->|No| S3
    end

Critical Sections

  1. Initialization: Create root node with empty children map
  2. Insert Loop: Traverse or create nodes for each character
  3. Search Loop: Traverse existing nodes, fail if any character missing
  4. End Marker: Always check isEndOfWord to distinguish words from prefixes

Variations

Wildcard Search Template

Supports . as wildcard matching any character. Solves LeetCode 211: Design Add and Search Words Data Structure.

class WordDictionary {
    constructor() {
        this.root = new TrieNode();
    }

    addWord(word) {
        let node = this.root;
        for (const char of word) {
            if (!node.children.has(char)) {
                node.children.set(char, new TrieNode());
            }
            node = node.children.get(char);
        }
        node.isEndOfWord = true;
    }

    search(word) {
        return this._dfs(word, 0, this.root);
    }

    _dfs(word, index, node) {
        if (index === word.length) {
            return node.isEndOfWord;
        }

        const char = word[index];
        if (char === '.') {
            // Try all possible children
            for (const child of node.children.values()) {
                if (this._dfs(word, index + 1, child)) {
                    return true;
                }
            }
            return false;
        } else {
            if (!node.children.has(char)) {
                return false;
            }
            return this._dfs(word, index + 1, node.children.get(char));
        }
    }
}
class WordDictionary:
    def __init__(self):
        self.root = TrieNode()

    def add_word(self, word: str) -> None:
        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 search(self, word: str) -> bool:
        return self._dfs(word, 0, self.root)

    def _dfs(self, word: str, index: int, node) -> bool:
        if index == len(word):
            return node.is_end_of_word

        char = word[index]
        if char == '.':
            # Try all possible children
            for child in node.children.values():
                if self._dfs(word, index + 1, child):
                    return True
            return False
        else:
            if char not in node.children:
                return False
            return self._dfs(word, index + 1, node.children[char])
class WordDictionary {
    private TrieNode root;

    public WordDictionary() {
        root = new TrieNode();
    }

    public void addWord(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 search(String word) {
        return dfs(word, 0, root);
    }

    private boolean dfs(String word, int index, TrieNode node) {
        if (index == word.length()) {
            return node.isEndOfWord;
        }

        char ch = word.charAt(index);
        if (ch == '.') {
            // Try all possible children
            for (TrieNode child : node.children.values()) {
                if (dfs(word, index + 1, child)) {
                    return true;
                }
            }
            return false;
        } else {
            if (!node.children.containsKey(ch)) {
                return false;
            }
            return dfs(word, index + 1, node.children.get(ch));
        }
    }
}
class WordDictionary {
private:
    TrieNode* root;

    bool dfs(const std::string& word, int index, TrieNode* node) {
        if (index == word.length()) {
            return node->isEndOfWord;
        }

        char ch = word[index];
        if (ch == '.') {
            // Try all possible children
            for (const auto& pair : node->children) {
                if (dfs(word, index + 1, pair.second)) {
                    return true;
                }
            }
            return false;
        } else {
            if (node->children.find(ch) == node->children.end()) {
                return false;
            }
            return dfs(word, index + 1, node->children[ch]);
        }
    }

public:
    WordDictionary() {
        root = new TrieNode();
    }

    void addWord(const std::string& word) {
        TrieNode* node = root;
        for (char ch : word) {
            if (node->children.find(ch) == node->children.end()) {
                node->children[ch] = new TrieNode();
            }
            node = node->children[ch];
        }
        node->isEndOfWord = true;
    }

    bool search(const std::string& word) {
        return dfs(word, 0, root);
    }
};
type WordDictionary struct {
    root *TrieNode
}

func NewWordDictionary() *WordDictionary {
    return &WordDictionary{root: NewTrieNode()}
}

func (wd *WordDictionary) AddWord(word string) {
    node := wd.root
    for _, ch := range word {
        if node.children[ch] == nil {
            node.children[ch] = NewTrieNode()
        }
        node = node.children[ch]
    }
    node.isEndOfWord = true
}

func (wd *WordDictionary) Search(word string) bool {
    return wd.dfs(word, 0, wd.root)
}

func (wd *WordDictionary) dfs(word string, index int, node *TrieNode) bool {
    if index == len(word) {
        return node.isEndOfWord
    }

    ch := rune(word[index])
    if ch == '.' {
        // Try all possible children
        for _, child := range node.children {
            if wd.dfs(word, index+1, child) {
                return true
            }
        }
        return false
    } else {
        if node.children[ch] == nil {
            return false
        }
        return wd.dfs(word, index+1, node.children[ch])
    }
}
class WordDictionary
  def initialize
    @root = TrieNode.new
  end

  def add_word(word)
    node = @root
    word.each_char do |char|
      node.children[char] ||= TrieNode.new
      node = node.children[char]
    end
    node.is_end_of_word = true
  end

  def search(word)
    dfs(word, 0, @root)
  end

  private

  def dfs(word, index, node)
    return node.is_end_of_word if index == word.length

    char = word[index]
    if char == '.'
      # Try all possible children
      node.children.values.each do |child|
        return true if dfs(word, index + 1, child)
      end
      false
    else
      return false unless node.children[char]
      dfs(word, index + 1, node.children[char])
    end
  end
end

Array-Based Trie for Fixed Alphabets

More memory efficient for small, fixed character sets:

// For lowercase letters only (26 characters)
class ArrayTrieNode {
    constructor() {
        this.children = new Array(26).fill(null);
        this.isEndOfWord = false;
    }
}

class ArrayTrie {
    constructor() {
        this.root = new ArrayTrieNode();
    }

    _charIndex(char) {
        return char.charCodeAt(0) - 'a'.charCodeAt(0);
    }

    insert(word) {
        let node = this.root;
        for (const char of word) {
            const idx = this._charIndex(char);
            if (!node.children[idx]) {
                node.children[idx] = new ArrayTrieNode();
            }
            node = node.children[idx];
        }
        node.isEndOfWord = true;
    }

    search(word) {
        let node = this.root;
        for (const char of word) {
            const idx = this._charIndex(char);
            if (!node.children[idx]) return false;
            node = node.children[idx];
        }
        return node.isEndOfWord;
    }
}
# For lowercase letters only (26 characters)
class ArrayTrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.is_end_of_word = False

class ArrayTrie:
    def __init__(self):
        self.root = ArrayTrieNode()

    def _char_index(self, char: str) -> int:
        return ord(char) - ord('a')

    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            idx = self._char_index(char)
            if not node.children[idx]:
                node.children[idx] = ArrayTrieNode()
            node = node.children[idx]
        node.is_end_of_word = True

    def search(self, word: str) -> bool:
        node = self.root
        for char in word:
            idx = self._char_index(char)
            if not node.children[idx]:
                return False
            node = node.children[idx]
        return node.is_end_of_word
// For lowercase letters only (26 characters)
class ArrayTrieNode {
    ArrayTrieNode[] children;
    boolean isEndOfWord;

    public ArrayTrieNode() {
        children = new ArrayTrieNode[26];
        isEndOfWord = false;
    }
}

class ArrayTrie {
    private ArrayTrieNode root;

    public ArrayTrie() {
        root = new ArrayTrieNode();
    }

    private int charIndex(char ch) {
        return ch - 'a';
    }

    public void insert(String word) {
        ArrayTrieNode node = root;
        for (char ch : word.toCharArray()) {
            int idx = charIndex(ch);
            if (node.children[idx] == null) {
                node.children[idx] = new ArrayTrieNode();
            }
            node = node.children[idx];
        }
        node.isEndOfWord = true;
    }

    public boolean search(String word) {
        ArrayTrieNode node = root;
        for (char ch : word.toCharArray()) {
            int idx = charIndex(ch);
            if (node.children[idx] == null) return false;
            node = node.children[idx];
        }
        return node.isEndOfWord;
    }
}
// For lowercase letters only (26 characters)
class ArrayTrieNode {
public:
    ArrayTrieNode* children[26];
    bool isEndOfWord;

    ArrayTrieNode() : isEndOfWord(false) {
        for (int i = 0; i < 26; i++) {
            children[i] = nullptr;
        }
    }
};

class ArrayTrie {
private:
    ArrayTrieNode* root;

    int charIndex(char ch) {
        return ch - 'a';
    }

public:
    ArrayTrie() {
        root = new ArrayTrieNode();
    }

    void insert(const std::string& word) {
        ArrayTrieNode* node = root;
        for (char ch : word) {
            int idx = charIndex(ch);
            if (!node->children[idx]) {
                node->children[idx] = new ArrayTrieNode();
            }
            node = node->children[idx];
        }
        node->isEndOfWord = true;
    }

    bool search(const std::string& word) {
        ArrayTrieNode* node = root;
        for (char ch : word) {
            int idx = charIndex(ch);
            if (!node->children[idx]) return false;
            node = node->children[idx];
        }
        return node->isEndOfWord;
    }
};
// For lowercase letters only (26 characters)
type ArrayTrieNode struct {
    children    [26]*ArrayTrieNode
    isEndOfWord bool
}

type ArrayTrie struct {
    root *ArrayTrieNode
}

func NewArrayTrie() *ArrayTrie {
    return &ArrayTrie{root: &ArrayTrieNode{}}
}

func charIndex(ch rune) int {
    return int(ch - 'a')
}

func (t *ArrayTrie) Insert(word string) {
    node := t.root
    for _, ch := range word {
        idx := charIndex(ch)
        if node.children[idx] == nil {
            node.children[idx] = &ArrayTrieNode{}
        }
        node = node.children[idx]
    }
    node.isEndOfWord = true
}

func (t *ArrayTrie) Search(word string) bool {
    node := t.root
    for _, ch := range word {
        idx := charIndex(ch)
        if node.children[idx] == nil {
            return false
        }
        node = node.children[idx]
    }
    return node.isEndOfWord
}
# For lowercase letters only (26 characters)
class ArrayTrieNode
  attr_accessor :children, :is_end_of_word

  def initialize
    @children = Array.new(26)
    @is_end_of_word = false
  end
end

class ArrayTrie
  def initialize
    @root = ArrayTrieNode.new
  end

  def insert(word)
    node = @root
    word.each_char do |char|
      idx = char_index(char)
      node.children[idx] ||= ArrayTrieNode.new
      node = node.children[idx]
    end
    node.is_end_of_word = true
  end

  def search(word)
    node = @root
    word.each_char do |char|
      idx = char_index(char)
      return false unless node.children[idx]
      node = node.children[idx]
    end
    node.is_end_of_word
  end

  private

  def char_index(char)
    char.ord - 'a'.ord
  end
end

Practice

Ready to apply these templates? Head to the Practice Problems page for curated LeetCode problems that use the trie pattern.

Pro Tip: When adapting these templates, focus on the core loop structure. Most trie problems follow the same traversal pattern - the difference is what you do at each node or at the end of traversal.