Short Note on Trie

Last Updated : 23 Feb, 2026

A Trie is a special tree used to store words (or sequences of 0s and 1s). It is also called a prefix tree because it is very good at handling words that share the same starting letters.
Trie is used when we need to quickly find, search, or check words or prefixes efficiently, especially when dealing with a large collection of strings or when many words share common starting characters.

Properties of a Trie Data Structure

  • The root is the starting point of the Trie and represents an empty string.
  • Each node stores one character.
  • The path from the root to any node forms a word or a prefix.
  • Words with the same prefix share the same path, saving space.
  • A special flag (like isEndOfWord) is used to mark the end of a complete word.
root_node_

Representation of Trie Node

Each node contains:

  • Array of size 26 for children
  • A flag to mark End of word
C++
class TrieNode {
    
    // for storing 26 childrens
    TrieNode* children[26];
    
    bool isEndOfWord;

    TrieNode() {
        isEndOfWord = false;
        for (int i = 0; i < 26; i++)
            children[i] = NULL;
    }
};
Java
class TrieNode {

    // for storing 26 children
    TrieNode[] children;
    boolean isEndOfWord;

    TrieNode() {
        isEndOfWord = false;
        children = new TrieNode[26];
        for (int i = 0; i < 26; i++) {
            children[i] = null;
        }
    }
}
Python
class TrieNode:
    def __init__(self):
        # for storing 26 children
        self.children = [None] * 26
        self.isEndOfWord = False
C#
class TrieNode
{
    // for storing 26 children
    public TrieNode[] children;
    public bool isEndOfWord;

    public TrieNode()
    {
        isEndOfWord = false;
        children = new TrieNode[26];
        for (int i = 0; i < 26; i++)
        {
            children[i] = null;
        }
    }
}
JavaScript
class TrieNode {
    constructor() {
        // for storing 26 children
        this.children = new Array(26).fill(null);
        this.isEndOfWord = false;
    }
}

Insertion in Trie Data Structure

  • Start from root.
  • For each character - find index → (ch - 'a')
  • If child doesn’t exist, create it.
  • Move to the child.
  • After last character, mark isEndOfWord = true.
root_node


C++
void insert(TrieNode* root, string word) {
    TrieNode* curr = root;

    for (char ch : word) {
        
        // map char to index
        int index = ch - 'a';

        if (curr->children[index] == NULL){
            
            // create if missing
            curr->children[index] = new TrieNode();
        }

        curr = curr->children[index];
    }

     // mark end
    curr->isEndOfWord = true;
}
Java
void insert(TrieNode root, String word) {
    TrieNode curr = root;

    for (char ch : word.toCharArray()) {
        
        // map char to index
        int index = ch - 'a'; 

        if (curr.children[index] == null){
            
            // create if missing
            curr.children[index] = new TrieNode(); 
        }

        curr = curr.children[index];
    }
    
     // mark end
    curr.isEndOfWord = true;
}
Python
def insert(root, word):
    curr = root

    for ch in word:
        
        # map char
        index = ord(ch) - ord('a')  

        if curr.children[index] is None:
            
            # create if missing
            curr.children[index] = TrieNode()  

        curr = curr.children[index]
    
    # mark end
    curr.isEndOfWord = True  
C#
void Insert(TrieNode root, string word) {
    TrieNode curr = root;

    foreach (char ch in word) {
        
         // map char
        int index = ch - 'a';

        if (curr.children[index] == null)
        
            // create if missing
            curr.children[index] = new TrieNode(); 

        curr = curr.children[index];
    }
    
    // mark end
    curr.isEndOfWord = true; 
}
JavaScript
function insert(root, word) {
    let curr = root;

    for (let ch of word) {
        
        // map char
        let index = ch.charCodeAt(0) - 'a'.charCodeAt(0); 

        if (curr.children[index] === null){
            
            // create if missing
            curr.children[index] = new TrieNode(); 
        }

        curr = curr.children[index];
    }
    
    // mark end
    curr.isEndOfWord = true; 
}

Searching in Trie Data Structure

  • Start from root.
  • For each character , move to the corresponding child.
  • If child is NULL → word not present.
  • After last character, check isEndOfWord.
C++
bool search(TrieNode* root, string word) {
    TrieNode* curr = root;

    for (char ch : word) {
        
        // map char
        int index = ch - 'a';

        // not found
        if (curr->children[index] == NULL)
            return false;

        curr = curr->children[index];
    }

    // check end
    return curr->isEndOfWord;
}
Java
boolean search(TrieNode root, String word) {
    TrieNode curr = root;

    for (char ch : word.toCharArray()) {
        
        // map char
        int index = ch - 'a'; 

        // not found
        if (curr.children[index] == null)
            return false; 

        curr = curr.children[index];
    }

    // check end
    return curr.isEndOfWord; 
}
Python
def search(root, word):
    curr = root

    for ch in word:
        
        # map char
        index = ord(ch) - ord('a')  

        # not found
        if curr.children[index] is None:
            return False  

        curr = curr.children[index]

    # check end
    return curr.isEndOfWord  
C#
bool Search(TrieNode root, string word) {
    TrieNode curr = root;

    foreach (char ch in word) {
        
        // map char
        int index = ch - 'a'; 

        // not found
        if (curr.children[index] == null)
            return false; 

        curr = curr.children[index];
    }
    
    // check end
    return curr.isEndOfWord; 
}
JavaScript
function search(root, word) {
    let curr = root;

    for (let ch of word) {
        
        // map char
        let index = ch.charCodeAt(0) - 'a'.charCodeAt(0); 

        // not found
        if (curr.children[index] === null)
            return false; 

        curr = curr.children[index];
    }
    
    // check end
    return curr.isEndOfWord; 
}

Prefix Searching in Trie Data Structure

Checks if any word starts with given prefix.

C++
bool startsWith(TrieNode* root, string prefix) {
    TrieNode* curr = root;

    for (char ch : prefix) {
        
        // map char
        int index = ch - 'a';

        if (curr->children[index] == NULL){
            
            // prefix not found
            return false;
        }

        curr = curr->children[index];
    }

    // prefix exists
    return true;
}
Java
boolean startsWith(TrieNode root, String prefix) {
    TrieNode curr = root;

    for (char ch : prefix.toCharArray()) {
        
        // map char
        int index = ch - 'a'; 

        if (curr.children[index] == null){
            
            // prefix not found
            return false; 
        }

        curr = curr.children[index];
    }

    // prefix exists
    return true; 
}
Python
def startsWith(root, prefix):
    curr = root

    for ch in prefix:
        
         # map char
        index = ord(ch) - ord('a') 

        # prefix not found
        if curr.children[index] is None:
            return False  

        curr = curr.children[index]

    # prefix exists
    return True  
C#
bool StartsWith(TrieNode root, string prefix) {
    TrieNode curr = root;

    foreach (char ch in prefix) {
        
        // map char
        int index = ch - 'a'; 

        // prefix not found
        if (curr.children[index] == null)
            return false; 

        curr = curr.children[index];
    }

    // prefix exists
    return true; 
}
JavaScript
function startsWith(root, prefix) {
    let curr = root;

    for (let ch of prefix) {
        
         // map char
        let index = ch.charCodeAt(0) - 'a'.charCodeAt(0);

        // prefix not found
        if (curr.children[index] === null)
            return false; 

        curr = curr.children[index];
    }

    // prefix exists
    return true; 
}

Complexity Analysis of Trie Data Structure

operation

Binary Trie (Bit Trie) – Used for Bitwise Operations

In a Binary Trie, numbers are stored in their binary form instead of characters. Each node has only two children representing bit 0 and 1. This data structure is primarily used to efficiently perform various bitwise operations, especially XOR-based computations on numbers.

bit_trie_after_inserting_7_and_5_4_bit_C++
class TrieNode
{
    public:
    
    // 0 or 1
    TrieNode *children[2];
    TrieNode()
    {
        children[0] = children[1] = nullptr;
    }
};
Java
class TrieNode {
    TrieNode[] children;

    TrieNode() {
        
        // 0 and 1
        children = new TrieNode[2]; 
        children[0] = null;
        children[1] = null;
    }
}
Python
class TrieNode:
    def __init__(self):
        
        # 0 and 1
        self.children = [None, None]  
C#
class TrieNode {
    public TrieNode[] children;

    public TrieNode() {
        
        // 0 and 1
        children = new TrieNode[2]; 
        children[0] = null;
        children[1] = null;
    }
}
JavaScript
class TrieNode {
    constructor() {
        
        // 0 and 1
        this.children = [null, null]; 
    }
}

Example Problem - Find the Maximum XOR of Two Numbers in an Array

The array elements are stored in a Binary Trie using their binary representation. Since XOR is maximized when corresponding bits are different, each number is compared against the trie while preferring the opposite bit at every position (0 prefers 1, and 1 prefers 0). This greedy bit-by-bit matching ensures that the resulting XOR value is as large as possible. By inserting each number once and querying the trie once, the overall time complexity becomes O(N).

C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// trie node
class TrieNode {
public:
    TrieNode* children[2];

    TrieNode() {
        children[0] = children[1] = nullptr;
    }
};

// trie data structure
class Trie {
private:
    TrieNode* root;

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

    // Insert number into trie
    void insert(int num) {
        TrieNode* node = root;

        for (int i = 31; i >= 0; i--) {
            int bit = (num >> i) & 1;

            if (!node->children[bit])
                node->children[bit] = new TrieNode();

            node = node->children[bit];
        }
    }

    // Find maximum XOR of num with inserted numbers
    int getMaxXor(int num) {
        TrieNode* node = root;
        int maxXor = 0;

        for (int i = 31; i >= 0; i--) {
            int bit = (num >> i) & 1;
            int opposite = 1 - bit;

            if (node->children[opposite]) {
                maxXor |= (1 << i);
                node = node->children[opposite];
            } else {
                node = node->children[bit];
            }
        }

        return maxXor;
    }
};

int findMaximumXOR(vector<int>& nums) {
    Trie trie;
    int maxResult = 0;

    // insert every element to trie
    for (int num : nums)
        trie.insert(num);

    // for each number try to find the best pair
    for (int num : nums)
        maxResult = max(maxResult, trie.getMaxXor(num));

    return maxResult;
}

int main() {
    vector<int> nums = {3, 10, 5, 25, 2, 8};
    cout << findMaximumXOR(nums);
    return 0;
}
Java
class TrieNode {
    TrieNode[] children = new TrieNode[2];
}

// trie data structure
class Trie {
    private TrieNode root;

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

    // Insert number into trie
    public void insert(int num) {
        TrieNode node = root;

        for (int i = 31; i >= 0; i--) {

            int bit = (num >> i) & 1;

            if (node.children[bit] == null)
                node.children[bit] = new TrieNode();

            node = node.children[bit];
        }
    }

    // Find maximum XOR of num with inserted numbers
    public int getMaxXor(int num) {
        TrieNode node = root;
        int maxXor = 0;

        for (int i = 31; i >= 0; i--) {

            // get the current bit
            int bit = (num >> i) & 1;

            // get the opposite bit
            int opposite = 1 - bit;

            // try to find the opposite bit
            if (node.children[opposite] != null) {

                // if opposite present set that bit
                maxXor |= (1 << i);
                node = node.children[opposite];
            } else {

                // if not go with the same bit
                node = node.children[bit];
            }
        }

        return maxXor;
    }
}

public class GfG {
    public static int findMaximumXOR(int[] nums) {
        Trie trie = new Trie();
        int maxResult = 0;

        // insert every element to trie
        for (int num : nums)
            trie.insert(num);

        // for each number try to find the appropriate pair
        for (int num : nums)
            maxResult = Math.max(maxResult, trie.getMaxXor(num));

        return maxResult;
    }

    public static void main(String[] args) {
        int[] nums = {3, 10, 5, 25, 2, 8};
        System.out.println(findMaximumXOR(nums));
    }
}
Python
class TrieNode:
    def __init__(self):
        self.children = [None, None]


# trie data structure
class Trie:
    def __init__(self):
        self.root = TrieNode()

    # Insert number into trie
    def insert(self, num):
        node = self.root

        for i in range(31, -1, -1):
            bit = (num >> i) & 1

            if node.children[bit] is None:
                node.children[bit] = TrieNode()

            node = node.children[bit]

    # Find maximum XOR of num with inserted numbers
    def getMaxXor(self, num):
        node = self.root
        max_xor = 0

        for i in range(31, -1, -1):

            # get the current bit
            bit = (num >> i) & 1

            # get the opposite bit
            opposite = 1 - bit

            # try to find the opposite bit
            if node.children[opposite]:
                # if opposite present set that bit
                max_xor |= (1 << i)
                node = node.children[opposite]
            else:
                # if not go with the same bit
                node = node.children[bit]

        return max_xor


def findMaximumXOR(nums):
    trie = Trie()
    max_result = 0

    # insert every element to trie
    for num in nums:
        trie.insert(num)

    # for each number try to find the appropriate pair
    for num in nums:
        max_result = max(max_result, trie.getMaxXor(num))

    return max_result


if __name__ == "__main__":
    nums = [3, 10, 5, 25, 2, 8]
    print(findMaximumXOR(nums))
C#
using System;

class TrieNode {
    public TrieNode[] children = new TrieNode[2];
}

// trie data structure
class Trie {
    private TrieNode root;

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

    // Insert number into trie
    public void Insert(int num) {
        TrieNode node = root;

        for (int i = 31; i >= 0; i--) {

            int bit = (num >> i) & 1;

            if (node.children[bit] == null)
                node.children[bit] = new TrieNode();

            node = node.children[bit];
        }
    }

    // Find maximum XOR of num with inserted numbers
    public int GetMaxXor(int num) {
        TrieNode node = root;
        int maxXor = 0;

        for (int i = 31; i >= 0; i--) {

            // get the current bit
            int bit = (num >> i) & 1;

            // get the opposite bit
            int opposite = 1 - bit;

            // try to find the opposite bit
            if (node.children[opposite] != null) {

                // if opposite present set that bit
                maxXor |= (1 << i);
                node = node.children[opposite];
            } else {

                // if not go with the same bit
                node = node.children[bit];
            }
        }

        return maxXor;
    }
}

class GfG {
    static int FindMaximumXOR(int[] nums) {
        Trie trie = new Trie();
        int maxResult = 0;

        // insert every element to trie
        foreach (int num in nums)
            trie.Insert(num);

        // for each number try to find the appropriate pair
        foreach (int num in nums)
            maxResult = Math.Max(maxResult, trie.GetMaxXor(num));

        return maxResult;
    }

    static void Main() {
        int[] nums = {3, 10, 5, 25, 2, 8};
        Console.WriteLine(FindMaximumXOR(nums));
    }
}
JavaScript
class TrieNode {
    constructor() {
        this.children = [null, null];
    }
}

// trie data structure
class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    // Insert number into trie
    insert(num) {
        let node = this.root;

        for (let i = 31; i >= 0; i--) {

            let bit = (num >> i) & 1;

            if (!node.children[bit])
                node.children[bit] = new TrieNode();

            node = node.children[bit];
        }
    }

    // Find maximum XOR of num with inserted numbers
    getMaxXor(num) {
        let node = this.root;
        let maxXor = 0;

        for (let i = 31; i >= 0; i--) {

            // get the current bit
            let bit = (num >> i) & 1;

            // get the opposite bit
            let opposite = 1 - bit;

            // try to find the opposite bit
            if (node.children[opposite]) {

                // if opposite present set that bit
                maxXor |= (1 << i);
                node = node.children[opposite];
            } else {

                // if not go with the same bit
                node = node.children[bit];
            }
        }

        return maxXor;
    }
}

function findMaximumXOR(nums) {
    let trie = new Trie();
    let maxResult = 0;

    // insert every element to trie
    for (let num of nums)
        trie.insert(num);

    // for each number try to find the appropriate pair
    for (let num of nums)
        maxResult = Math.max(maxResult, trie.getMaxXor(num));

    return maxResult;
}

// driver code
let nums = [3, 10, 5, 25, 2, 8];
console.log(findMaximumXOR(nums));

Applications of Trie Data Structure

prefix_search_problems

Advantages and Disadvantages of Trie Data Structure


Comment