DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
FREEDOM251

Freedom251 › Blog › Algorithms

Trie Autocomplete from Scratch: Build a Prefix Search That Scales

By Freedom251 Team · · 9 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a trie when your application must answer prefix questions such as “which words start with ca?” A trie stores each string as a path of characters, sharing the nodes for common prefixes. Once the path for a prefix is found, autocomplete explores only that prefix’s subtree instead of comparing the prefix with every stored word.

This guide builds a working trie from scratch, including insertion, exact search, prefix checks, bounded autocomplete, and safe deletion. It also explains the memory cost, the assumptions behind the usual O(L) claims, and when a hash set or compressed trie is a better choice.

As an Amazon Associate I earn from qualifying purchases.

What a trie stores

A trie (pronounced “try,” from retrieval) has a root node representing the empty string. Each outgoing edge carries one character, and every node therefore represents the prefix reached from the root.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
root
 └─ c
    └─ a
       ├─ t (is_word = true)       "cat"
       └─ r
          └─ (is_word = true)      "car"

The is_word flag is essential. In this example, ca is a valid path but is not necessarily a stored word; cat and car are complete keys because their final nodes are marked. Without that terminal marker, exact search could incorrectly report every prefix of a longer word as present.

Shared prefixes are the reason a trie helps with autocomplete. The strings cat, car, and cart reuse the same c → a → r nodes. The trade-off is one object per character (in a basic implementation), plus a child mapping on each object.

Decide the input model before writing code

Character set

A fixed lowercase-English dictionary can use the alphabet a through z. A general text field may contain uppercase letters, punctuation, emoji, or characters from several scripts. A dictionary-based child map works for all of these, provided the keys are individual characters in your language runtime.

Case and normalization

Choose one policy and apply it to both inserted keys and queries. Common policies are case-sensitive storage, lowercasing for case-insensitive search, or Unicode normalization followed by case folding. Normalization can change how visually equivalent sequences compare, so document it as part of the API rather than silently mixing policies.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Child-container choice

Representation Strength Cost or limitation
Map/dictionary Supports sparse children and broad character sets Each node carries map overhead; lookup is expected O(1) in typical hash-map runtimes
Fixed array Fast indexing for a small, known alphabet Allocates slots even for absent characters; awkward for Unicode
Sorted child list Can reduce map overhead in tiny sparse nodes Child lookup may require a scan or binary search

The implementation below uses a dictionary because it is easy to understand and does not assume an alphabet. Its complexity statements rely on the runtime offering expected constant-time dictionary access.

Rank #2
Sale
Introduction to Algorithms, fourth edition
  • color: White
  • INTRODUCTION TO ALGORITHMS, FOURTH EDITION

Build the trie in Python

The root is created once and represents the empty prefix. Every other node stores its outgoing edges in children and records whether a complete key ends there.

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_word = False


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

    def insert(self, word):
        """Store word; inserting an existing word is harmless."""
        node = self.root
        for ch in word:
            node = node.children.setdefault(ch, TrieNode())
        node.is_word = True

    def search(self, word):
        """Return True only when word was inserted as a complete key."""
        node = self._node_for(word)
        return node is not None and node.is_word

    def starts_with(self, prefix):
        """Return True when the character path for prefix exists."""
        return self._node_for(prefix) is not None

    def _node_for(self, text):
        node = self.root
        for ch in text:
            node = node.children.get(ch)
            if node is None:
                return None
        return node

    def autocomplete(self, prefix, limit=None):
        """Return completions in depth-first, child-insertion order."""
        start = self._node_for(prefix)
        if start is None:
            return []

        results = []

        def visit(node, word):
            if limit is not None and len(results) >= limit:
                return
            if node.is_word:
                results.append(word)
            for ch, child in node.children.items():
                visit(child, word + ch)
                if limit is not None and len(results) >= limit:
                    return

        visit(start, prefix)
        return results

    def delete(self, word):
        """Remove word and prune nodes no longer needed by another key."""
        path = []
        node = self.root
        for ch in word:
            child = node.children.get(ch)
            if child is None:
                return False
            path.append((node, ch, child))
            node = child

        if not node.is_word:
            return False
        node.is_word = False

        # Work upward, removing only leaf nodes that are not word endings.
        for parent, ch, child in reversed(path):
            if child.is_word or child.children:
                break
            del parent.children[ch]
        return True

setdefault creates a child only when the character is missing. The private _node_for helper is shared by exact search, prefix lookup, and completion, which keeps their path-walking behavior consistent.

How each operation behaves

Insertion

To insert car, begin at the root, create or follow the c child, then a, then r. Mark the final node. If cat is inserted afterward, only the new t node is needed because c and a already exist.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Exact search

search("car") follows all three characters and then checks is_word. Searching for ca returns false when only car is stored, even though the path exists. Searching for a missing edge returns false immediately.

Rank #3
Sale
Data Structures and Algorithms in Python
  • Used Book in Good Condition

Prefix lookup

starts_with("ca") succeeds as soon as the c → a path is found. It does not inspect the terminal flag because a prefix need not be a complete key. An empty prefix follows zero edges and therefore matches the root; decide whether your product should treat that as valid before exposing it in a user interface.

Autocomplete

Completion has two distinct stages:

  1. Walk the requested prefix to locate its node. If any character is absent, return an empty list.
  2. Traverse descendants, carrying the full text accumulated so far and emitting every node whose is_word flag is true.

The method accepts an optional limit. A limit prevents a broad prefix such as an empty string or a from materializing an unbounded result set. The sample returns depth-first results in dictionary insertion order; that is an implementation detail, not a relevance ranking.

Ordering, ranking, and result limits

Users usually expect suggestions in a deliberate order. Alphabetical order requires sorting children or sorting the completed result list. Frequency, recency, locale, spelling quality, and personalization require metadata beyond a plain terminal flag.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep ordering separate from prefix discovery. A practical design is to store a score or popularity value at terminal nodes, collect at most a bounded number of candidates, and rank them according to a documented policy. If you need globally best results under a prefix, a simple depth-first walk may visit many low-ranked descendants before finding the best matches; maintaining ranked child lists or using a specialized top-k structure can reduce that work at the cost of more update complexity.

Deleting a key without breaking shared prefixes

Deletion has two cases. If car and cart are stored, deleting car must clear the terminal flag at r but retain the path because cart still uses it. If cart is then deleted, the now-unneeded t node can be removed, followed by any parent nodes that have no children and are not terminal nodes.

The implementation records the path while searching, clears the final marker, and prunes upward until it reaches a shared branch or another stored word. It returns False when the key was absent or was only a prefix, so callers can distinguish a real deletion from a no-op.

Complexity: what O(L) really means

Let L be the length of the inserted word or queried prefix. With expected constant-time map operations, insertion, exact search, and prefix lookup each walk at most L edges.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Operation Time Why
Insert O(L) One child lookup or creation per character
Exact search O(L) One path walk plus a terminal-flag check
Prefix lookup O(L) Stops after the prefix path is consumed
Autocomplete O(L + V + R) L to find the prefix, then V traversal work and R emitted results
Delete O(L)
One downward walk and at most L upward pruning checks

For autocomplete, V is the amount of the matching subtree visited; it can be much larger than the number of returned results when ranking, filtering, or a late limit is used. The cost also includes constructing the output strings. If child containers are arrays, lookup is direct for the supported alphabet. If they are sorted lists or another structure, include that child-lookup cost instead of assuming constant time.

Best Value
Sale
Data Structures and Algorithms Made Easy: Data Structures and Algorithmic Puzzles
  • Binding: paperback
  • Language: english
  • It ensures you get the best usage for a longer period
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Trie versus other storage choices

Need Basic trie Hash set Sorted list Compressed (radix) trie
Exact membership Fast by key length, but node overhead may be unnecessary Usually the simplest and most memory-efficient choice Typically requires binary search Fast, with more complex node handling
Prefix existence Natural path walk Requires scanning or an additional index Can use a range of neighboring entries Natural path walk over longer edge labels
Autocomplete Descend directly from the prefix node No direct operation Scan the matching sorted range Descend with fewer nodes
Memory behavior One node per character plus child containers; sharing helps when prefixes repeat Stores complete keys and hash-table overhead Stores complete keys and list overhead Collapses single-child chains to reduce node count
Updates Insertion and pruning are straightforward Very straightforward for membership Insertion can shift elements or require a tree Splitting and merging edge labels add implementation work

If your only question is “is this exact identifier present?”, a hash set is generally simpler. Choose a trie when prefix queries are central and the memory budget can accommodate its per-node overhead. A radix or compressed trie is worth considering when many branches contain long runs of single children, because it stores those runs as one multi-character edge.

Memory and production considerations

  • Measure node count. A dictionary child map can dominate memory when most nodes have only one child. Prefix sharing reduces stored characters but does not remove object and map overhead.
  • Bound output. Require a maximum number of suggestions and, where appropriate, a maximum prefix length. This protects latency and response size for broad queries.
  • Keep normalization consistent. Applying lowercasing during insertion but not during lookup creates apparently missing words; applying it only at lookup can create collisions that were not intended.
  • Separate text from metadata. Popularity, locale, display labels, and permissions belong on terminal records or an associated data store rather than being encoded in the character path.
  • Consider concurrency. Read-heavy services can build an immutable trie and replace it atomically when a new snapshot is ready. In-place updates need synchronization appropriate to the language runtime.
  • Protect sensitive data. A trie can expose every stored key through broad prefix queries. Apply authorization and filtering before returning suggestions, especially for private names or identifiers.

Testing checklist

Use a small fixture that includes shared prefixes, complete words that are also prefixes, and a missing branch:

words = ["car", "cart", "cat", "dog"]
trie = Trie()
for word in words:
    trie.insert(word)

assert trie.search("car")
assert trie.search("cart")
assert not trie.search("ca")
assert trie.starts_with("ca")
assert not trie.starts_with("cow")
assert set(trie.autocomplete("car")) == {"car", "cart"}
assert trie.autocomplete("zz") == []
assert trie.delete("car")
assert not trie.search("car")
assert trie.search("cart")
assert not trie.delete("car")

Add tests for the empty string policy, repeated insertion, case and Unicode normalization, a limit of zero, a limit larger than the available results, deleting a missing key, and deleting a word that is a prefix of another word. If you promise alphabetical or ranked output, assert that ordering explicitly rather than relying on dictionary iteration order.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing the right design

Build the basic map-backed trie when you need predictable prefix operations, a transparent implementation, and moderate data volume. Use a hash set for exact membership without completion. Use a sorted representation when you already maintain ordered records and can tolerate scanning a prefix range. Move to a compressed trie when profiling shows that object and map overhead—not character-walk time—is the limiting factor.

The core idea remains small: walk characters to find a prefix, mark complete keys separately, and traverse only the descendants needed for the requested suggestions. The quality of a real autocomplete feature then depends on the policies around that structure—normalization, limits, ordering, ranking, authorization, and update strategy.

Quick Recap

SaleBestseller No. 2
Introduction to Algorithms, fourth edition
Introduction to Algorithms, fourth edition
color: White; INTRODUCTION TO ALGORITHMS, FOURTH EDITION
$99.47
SaleBestseller No. 3
Data Structures and Algorithms in Python
Data Structures and Algorithms in Python
Used Book in Good Condition
$125.13
SaleBestseller No. 5
Data Structures and Algorithms Made Easy: Data Structures and Algorithmic Puzzles
Data Structures and Algorithms Made Easy: Data Structures and Algorithmic Puzzles
Binding: paperback; Language: english; It ensures you get the best usage for a longer period
$29.41

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

AlgorithmsAutoCompleteData Structuresprefix searchPythontries

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.