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.

Representation of Trie Node
Each node contains:
- Array of size 26 for children
- A flag to mark End of word
class TrieNode {
// for storing 26 childrens
TrieNode* children[26];
bool isEndOfWord;
TrieNode() {
isEndOfWord = false;
for (int i = 0; i < 26; i++)
children[i] = NULL;
}
};
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;
}
}
}
class TrieNode:
def __init__(self):
# for storing 26 children
self.children = [None] * 26
self.isEndOfWord = False
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;
}
}
}
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.

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;
}
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;
}
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
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;
}
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.
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;
}
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;
}
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
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;
}
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.
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;
}
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;
}
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
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;
}
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

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.

class TrieNode
{
public:
// 0 or 1
TrieNode *children[2];
TrieNode()
{
children[0] = children[1] = nullptr;
}
};
class TrieNode {
TrieNode[] children;
TrieNode() {
// 0 and 1
children = new TrieNode[2];
children[0] = null;
children[1] = null;
}
}
class TrieNode:
def __init__(self):
# 0 and 1
self.children = [None, None]
class TrieNode {
public TrieNode[] children;
public TrieNode() {
// 0 and 1
children = new TrieNode[2];
children[0] = null;
children[1] = null;
}
}
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).
#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;
}
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));
}
}
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))
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));
}
}
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
