A Trie (pronounced “try”), or prefix tree, stores a set of strings by sharing their prefixes. Each edge is labeled by a character; walking from the root and reading labels spells prefixes. We mark nodes where a word ends (a boolean flag, often called isWord or end).
a..z and auto-lowercase user input.end=true.end=true.k words.| Operation | Time | Space (per insert) | Notes | 
|---|---|---|---|
| insert / search / startsWith | O(L) | O(L) | L = length of word/prefix | 
| autocomplete (k results) | O(L + k·avgLen) | — | Traverse prefix then collect | 
| delete | O(L) | — | Unmark + prune unused nodes | 
| memory | — | O(total chars) | Prefix sharing saves space; compare to HashSet strings | 
char c; left < c < right; eq to advance). Space-efficient with sorted-array performance characteristics.dict children
class Node:
    __slots__ = ("end","ch")
    def __init__(self): self.end=False; self.ch={}
class Trie:
    def __init__(self): self.root=Node()
    def insert(self, w: str) -> None:
        u=self.root
        for c in w:
            if c not in u.ch: u.ch[c]=Node()
            u=u.ch[c]
        u.end=True
    def search(self, w: str) -> bool:
        u=self.root
        for c in w:
            u=u.ch.get(c); 
            if u is None: return False
        return u.end
    def startsWith(self, p: str) -> bool:
        u=self.root
        for c in p:
            u=u.ch.get(c)
            if u is None: return False
        return True
    def delete(self, w: str) -> None:
        def rec(u,i):
            if i==len(w):
                if not u.end: return False, False
                u.end=False
                return True, (not u.ch)
            c=w[i]
            v=u.ch.get(c)
            if v is None: return False, False
            existed, prune = rec(v,i+1)
            if prune: u.ch.pop(c, None)
            return existed, (not u.end and not u.ch)
        rec(self.root,0)
        HashMap<Character,Node>
import java.util.*;
class Trie {
  static class Node {
    boolean end;
    Map<Character,Node> ch=new HashMap<>();
  }
  private final Node root=new Node();
  public void insert(String w){
    Node u=root;
    for(char cc: w.toCharArray()){
      char c=(char)('a'+(cc-'a'));             // assume cleaned to a..z
      u.ch.putIfAbsent(c, new Node());
      u=u.ch.get(c);
    }
    u.end=true;
  }
  public boolean search(String w){
    Node u=root;
    for(char c: w.toCharArray()){
      u=u.ch.get(c);
      if(u==null) return false;
    }
    return u.end;
  }
  public boolean startsWith(String p){
    Node u=root;
    for(char c: p.toCharArray()){
      u=u.ch.get(c);
      if(u==null) return false;
    }
    return true;
  }
  public void delete(String w){
    rec(root,w,0);
  }
  private boolean rec(Node u, String w, int i){
    if(i==w.length()){
      if(!u.end) return false;
      u.end=false;
      return u.ch.isEmpty();
    }
    Node v=u.ch.get(w.charAt(i));
    if(v==null) return false;
    boolean prune = rec(v,w,i+1);
    if(prune) u.ch.remove(w.charAt(i));
    return !u.end && u.ch.isEmpty();
  }
}
        unordered_map<char,Node*>
#include <unordered_map>
#include <string>
using namespace std;
struct Node{
  bool end=false;
  unordered_map<char,Node*> ch;
};
struct Trie{
  Node* root = new Node();
  void insert(const string& w){
    Node* u=root;
    for(char c: w){
      if(!u->ch.count(c)) u->ch[c]=new Node();
      u=u->ch[c];
    }
    u->end=true;
  }
  bool search(const string& w){
    Node* u=root;
    for(char c: w){ auto it=u->ch.find(c); if(it==u->ch.end()) return false; u=it->second; }
    return u->end;
  }
  bool startsWith(const string& p){
    Node* u=root;
    for(char c: p){ auto it=u->ch.find(c); if(it==u->ch.end()) return false; u=it->second; }
    return true;
  }
  void erase(const string& w){ rec(root,w,0); }
  bool rec(Node* u, const string& w, int i){
    if(i==(int)w.size()){ if(!u->end) return false; u->end=false; return u->ch.empty(); }
    auto it=u->ch.find(w[i]); if(it==u->ch.end()) return false;
    bool prune = rec(it->second,w,i+1);
    if(prune) u->ch.erase(w[i]);
    return !u->end && u->ch.empty();
  }
};
        Edges carry strings instead of single chars. During traversal, we match the longest common prefix of the remaining query and the edge label.
// Node has map<char, (label, child)>; label is string segment
insert(w):
  u = root
  i = 0
  while i < len(w):
    c = w[i]
    if no edge from u starting with c: add edge (w[i..], new node end=true); return
    (lab, v) = edge[u][c]
    k = LCP(lab, w[i..])
    if k == len(lab): u = v; i += k; continue
    // split edge at k
    mid = new node
    edge[u][c] = (lab[0..k), mid]
    edge[mid][lab[k]] = (lab[k..], v)
    if k == len(w)-i: mid.end = true
    else edge[mid][w[i+k]] = (w[i+k..], new node end=true)
    return
search(w):
  u=root; i=0
  while i < len(w):
    c=w[i]; if no edge[u][c]: return false
    (lab,v)=edge[u][c]
    if w[i..].startsWith(lab): i+=len(lab); u=v
    else return false
  return u.end
        Each node stores one character c and three pointers: lo for characters < c, eq to advance to the next character (when equal), and hi for > c. Words end by setting end=true at the node that matched the final character.
class TNode:
    __slots__=("c","lo","eq","hi","end")
    def __init__(self,c): self.c=c; self.lo=self.eq=self.hi=None; self.end=False
class TST:
    def __init__(self): self.root=None
    def insert(self, w):
        def rec(u, i):
            c=w[i]
            if u is None: u=TNode(c)
            if c < u.c: u.lo = rec(u.lo,i)
            elif c > u.c: u.hi = rec(u.hi,i)
            else:
                if i+1==len(w): u.end=True
                else: u.eq = rec(u.eq,i+1)
            return u
        if w: self.root = rec(self.root,0)
    def search(self, w):
        u=self.root; i=0
        while u:
            c=w[i]
            if c < u.c: u=u.lo
            elif c > u.c: u=u.hi
            else:
                if i+1==len(w): return u.end
                i+=1; u=u.eq
        return False
        
class TST {
  static class Node { char c; Node lo,eq,hi; boolean end; Node(char c){this.c=c;} }
  Node root;
  public void insert(String w){ root = ins(root,w,0); }
  private Node ins(Node u, String w, int i){
    char c=w.charAt(i);
    if(u==null) u=new Node(c);
    if(c<u.c) u.lo=ins(u.lo,w,i);
    else if(c>u.c) u.hi=ins(u.hi,w,i);
    else if(i+1==w.length()) u.end=true;
    else u.eq=ins(u.eq,w,i+1);
    return u;
  }
  public boolean search(String w){
    Node u=root; int i=0;
    while(u!=null){
      char c=w.charAt(i);
      if(c<u.c) u=u.lo;
      else if(c>u.c) u=u.hi;
      else { if(i+1==w.length()) return u.end; i++; u=u.eq; }
    }
    return false;
  }
}
        
struct TNode{ char c; bool end=false; TNode* lo=nullptr; TNode* eq=nullptr; TNode* hi=nullptr; TNode(char c):c(c){} };
TNode* ins(TNode* u, const string& w, int i){
  char c=w[i];
  if(!u) u=new TNode(c);
  if(c<u->c) u->lo=ins(u->lo,w,i);
  else if(c>u->c) u->hi=ins(u->hi,w,i);
  else if(i+1==(int)w.size()) u->end=true;
  else u->eq=ins(u->eq,w,i+1);
  return u;
}
bool search(TNode* u, const string& w){
  int i=0;
  while(u){
    char c=w[i];
    if(c<u->c) u=u->lo;
    else if(c>u->c) u=u->hi;
    else { if(i+1==(int)w.size()) return u->end; i++; u=u->eq; }
  }
  return false;
}
        set of words + bisect over a sorted list can do prefix ranges (O(log n + k)), but Tries win when many prefixes or character-by-character traversal is needed.TreeSet supports subSet(prefixRange) to emulate prefix queries; HashSet for exact words.std::set/std::map support prefix range via lower_bound/upper_bound.O(L) regardless of the number of words.O(L + k) behavior when results are limited to k.