If at first you don't succeed,
Trie, Trie again.

Meet the Trie (a.k.a Prefix Tree)

class TrieNode {
constructor() {
this.children = {}; // maps characters -> TrieNode
this.isEndOfWord = false;
}
}
class Trie {
constructor() {
this.root = new TrieNode();
}
}
The Basics

insert(word) {
let node = this.root;
for (let char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
}
node.isEndOfWord = true;
}Getting things in

search(word) {
let node = this.root;
for (let char of word) {
if (!node.children[char]) {
return false;
}
node = node.children[char];
}
return node.isEndOfWord;
}
Where is it?

startsWith(prefix) {
let node = this.root;
for (let char of prefix) {
if (!node.children[char]) {
return false;
}
node = node.children[char];
}
return true;
}Maybe just a bit

Cool story bro, but like, what now?

A real-er example

Auto Complete
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
body {
font-family: sans-serif;
padding: 40px;
max-width: 700px;
margin: auto;
}
input {
width: 100%;
padding: 12px;
font-size: 18px;
border: 2px solid #ccc;
border-radius: 8px;
}
ul {
list-style: none;
padding: 0;
margin-top: 10px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
li {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
}
li:last-child {
border-bottom: none;
}
li:hover,
li.active {
background: #f0f0f0;
}
.match {
font-weight: bold;
color: darkblue;
}
.hint {
margin-top: 8px;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<h1>Trie-based Autocomplete</h1>
<p>Start typing a JavaScript-related term:</p>
<input id="search" placeholder="Try: rea, dom, async..." />
<p class="hint">
↑ ↓ to navigate • Enter to select • Esc to close • Top 10 matches
</p>
<ul id="suggestions"></ul>
<script>
// ----------------------------
// Trie Implementation
// ----------------------------
class TrieNode {
constructor() {
this.children = {};
this.isEndOfWord = false;
}
}
class Trie {
constructor() {
this.root = new TrieNode();
}
insert(word) {
let node = this.root;
for (let char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
}
node.isEndOfWord = true;
}
_collectWords(node, prefix, results, limit) {
if (results.length >= limit) return;
if (node.isEndOfWord) {
results.push(prefix);
}
for (let char in node.children) {
if (results.length >= limit) return;
this._collectWords(node.children[char], prefix + char, results, limit);
}
}
autocomplete(prefix, limit = 10) {
let node = this.root;
for (let char of prefix) {
if (!node.children[char]) return [];
node = node.children[char];
}
let results = [];
this._collectWords(node, prefix, results, limit);
return results;
}
}
// ----------------------------
// Example Word List (~200)
// ----------------------------
const words = [
"array", "arrowfunction", "async", "await", "api",
"babel", "bind", "boolean", "browser",
"callback", "canvas", "catch", "chrome", "class",
"closure", "component", "const", "constructor",
"context", "cors", "cssom",
"data", "debugger", "declaration", "default",
"deno", "destructuring", "dom", "document",
"ecmascript", "event", "eventloop", "express", "export",
"fetch", "finally", "firebase", "function",
"garbagecollection", "generator", "github", "global", "graphql",
"hoisting", "hook", "html", "http",
"immutable", "import", "inheritance", "instanceof",
"javascript", "jest", "json", "jsx",
"koa",
"let", "library", "lint", "lodash",
"map", "middleware", "minification", "module",
"mongodb", "mocha",
"node", "nodejs", "npm",
"object", "oop", "operator",
"package", "parse", "polyfill", "promise",
"prototype", "proxy",
"queryselector",
"react", "reactdom", "reactrouter", "reducer",
"referenceerror", "regexp", "render",
"scope", "script", "server", "settimeout",
"spread", "state", "string", "svelte", "symbol",
"tailwind", "template", "test", "this", "throw",
"typescript",
"undefined", "url",
"variable", "vite", "vue",
"webpack", "websocket", "webworker", "webassembly",
"xhr", "yarn", "yield", "zod"
];
// ----------------------------
// Build Trie
// ----------------------------
const trie = new Trie();
words.forEach(word => trie.insert(word));
// ----------------------------
// UI Logic (Highlight + Keyboard)
// ----------------------------
const input = document.getElementById("search");
const suggestionsList = document.getElementById("suggestions");
let activeIndex = -1;
let currentSuggestions = [];
// Highlight matching prefix
function highlightMatch(word, prefix) {
const start = word.slice(0, prefix.length);
const rest = word.slice(prefix.length);
return `<span class="match">${start}</span>${rest}`;
}
// Render suggestion list
function renderSuggestions(prefix) {
suggestionsList.innerHTML = "";
activeIndex = -1;
if (!prefix) {
currentSuggestions = [];
return;
}
currentSuggestions = trie.autocomplete(prefix, 10);
currentSuggestions.forEach((word, index) => {
const li = document.createElement("li");
li.innerHTML = highlightMatch(word, prefix);
li.addEventListener("click", () => {
input.value = word;
suggestionsList.innerHTML = "";
});
suggestionsList.appendChild(li);
});
}
// Update active highlight
function updateActiveItem() {
const items = suggestionsList.querySelectorAll("li");
items.forEach(item => item.classList.remove("active"));
if (activeIndex >= 0 && items[activeIndex]) {
items[activeIndex].classList.add("active");
}
}
// Input typing handler
input.addEventListener("input", () => {
const value = input.value.trim().toLowerCase();
renderSuggestions(value);
});
// Keyboard navigation handler
input.addEventListener("keydown", (e) => {
if (currentSuggestions.length === 0) return;
if (e.key === "ArrowDown") {
activeIndex++;
if (activeIndex >= currentSuggestions.length) activeIndex = 0;
updateActiveItem();
}
if (e.key === "ArrowUp") {
activeIndex--;
if (activeIndex < 0) activeIndex = currentSuggestions.length - 1;
updateActiveItem();
}
if (e.key === "Enter") {
if (activeIndex >= 0) {
input.value = currentSuggestions[activeIndex];
suggestionsList.innerHTML = "";
currentSuggestions = [];
}
}
if (e.key === "Escape") {
suggestionsList.innerHTML = "";
currentSuggestions = [];
}
});
</script>
</body>
</html>
But how?
_collectWords(node, prefix, results, limit) {
if (results.length >= limit) return;
if (node.isEndOfWord) {
results.push(prefix);
}
for (let char in node.children) {
if (results.length >= limit) return;
this._collectWords(node.children[char], prefix + char, results, limit);
}
}
autocomplete(prefix, limit = 10) {
let node = this.root;
for (let char of prefix) {
if (!node.children[char]) return [];
node = node.children[char];
}
let results = [];
this._collectWords(node, prefix, results, limit);
return results;
}
Fuzzy Matching

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
body {
font-family: sans-serif;
padding: 40px;
max-width: 750px;
margin: auto;
}
input {
width: 100%;
padding: 12px;
font-size: 18px;
border: 2px solid #ccc;
border-radius: 10px;
outline: none;
}
input:focus {
border-color: #444;
}
ul {
list-style: none;
padding: 0;
margin-top: 12px;
border: 1px solid #ddd;
border-radius: 10px;
overflow: hidden;
}
li {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
font-size: 16px;
}
li:last-child {
border-bottom: none;
}
li:hover,
li.active {
background: #f0f0f0;
}
.match {
font-weight: bold;
color: darkblue;
}
.hint {
margin-top: 10px;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<h1>Trie Autocomplete + Fuzzy Search</h1>
<p>Start typing a JS related word (typos allowed):</p>
<input
id="search"
placeholder="Try: reac, javsacript, promse..."
autocomplete="off"
/>
<p class="hint">
↑ ↓ navigate • Enter select • Esc close • Top 10 • Max typo distance = 1
</p>
<ul id="suggestions"></ul>
<script>
// =====================================================
// Trie Implementation (with Fuzzy Search)
// =====================================================
class TrieNode {
constructor() {
this.children = {};
this.isEndOfWord = false;
}
}
class Trie {
constructor() {
this.root = new TrieNode();
}
insert(word) {
let node = this.root;
for (let char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
}
node.isEndOfWord = true;
}
// -----------------------------------------------------
// FUZZY AUTOCOMPLETE (Levenshtein-style Trie DFS)
// maxEdits = number of typos allowed
// -----------------------------------------------------
fuzzyAutocomplete(input, maxEdits = 1, limit = 10) {
let results = new Set();
const dfs = (node, wordSoFar, index, editsUsed) => {
if (results.size >= limit) return;
if (editsUsed > maxEdits) return;
// If we've consumed all input characters
if (index === input.length) {
if (node.isEndOfWord) {
results.add(wordSoFar);
}
// Allow extra letters beyond input (insertion)
for (let char in node.children) {
dfs(node.children[char], wordSoFar + char, index, editsUsed + 1);
}
return;
}
const currentChar = input[index];
// Case 1: Exact match
if (node.children[currentChar]) {
dfs(
node.children[currentChar],
wordSoFar + currentChar,
index + 1,
editsUsed
);
}
// Case 2: Substitution (wrong character)
for (let char in node.children) {
if (char !== currentChar) {
dfs(
node.children[char],
wordSoFar + char,
index + 1,
editsUsed + 1
);
}
}
// Case 3: Deletion (skip input character)
dfs(node, wordSoFar, index + 1, editsUsed + 1);
// Case 4: Insertion (extra character added)
for (let char in node.children) {
dfs(
node.children[char],
wordSoFar + char,
index,
editsUsed + 1
);
}
};
dfs(this.root, "", 0, 0);
return [...results].slice(0, limit);
}
}
// =====================================================
// JavaScript-Themed Word List (~200)
// =====================================================
const words = [
"array", "async", "await", "api", "argument",
"babel", "backend", "bind", "boolean", "browser",
"buffer", "bundler",
"callback", "canvas", "catch", "chai", "chrome",
"class", "closure", "cli", "component", "const",
"constructor", "context", "cors", "cookie",
"data", "datatype", "debugger", "declaration",
"default", "deno", "destructuring", "dom",
"document", "docker",
"ecmascript", "event", "eventloop", "exception",
"express", "export", "eslint",
"fetch", "finally", "firebase", "framework",
"frontend", "fullstack", "function",
"garbagecollection", "generator", "getelementbyid",
"github", "global", "graphql",
"hashmap", "heroku", "hoisting", "hook",
"html", "http", "https",
"immutable", "import", "indexdb", "inheritance",
"instanceof", "iteration",
"javascript", "jest", "json", "jsx", "jwt",
"koa", "kubernetes",
"lambda", "let", "library", "lint", "lodash",
"localstorage", "loop",
"map", "memoization", "middleware", "minification",
"module", "mongodb", "mocha", "mutationobserver",
"nan", "nestjs", "netlify", "nextjs",
"node", "nodejs", "npm", "nuxt",
"object", "observer", "oop", "operator",
"openapi", "oauth",
"package", "parse", "performance", "pipe",
"polyfill", "promise", "prototype", "proxy",
"prisma", "prettier",
"queryselector", "queue", "queuemicrotask",
"react", "reactdom", "reactrouter", "reducer",
"referenceerror", "regexp", "render", "rest",
"runtime", "rxjs",
"scope", "script", "server", "serverless",
"sessionstorage", "settimeout", "singleton",
"solidjs", "spread", "spa", "sql", "stack",
"state", "static", "string", "svelte", "symbol",
"tailwind", "template", "test", "this",
"throw", "token", "tree", "trycatch",
"typescript",
"undefined", "unittest", "unpkg", "url",
"urlparams",
"value", "variable", "vdom", "vercel",
"virtualmachine", "vite", "vue",
"webpack", "websocket", "webworker",
"webassembly", "webrtc", "window",
"xhr", "xmlhttprequest",
"yarn", "yield",
"zod"
];
// =====================================================
// Build Trie
// =====================================================
const trie = new Trie();
words.forEach(word => trie.insert(word));
// =====================================================
// UI + Highlight + Keyboard Navigation
// =====================================================
const input = document.getElementById("search");
const suggestionsList = document.getElementById("suggestions");
let activeIndex = -1;
let currentSuggestions = [];
// Highlight matching letters (best effort)
function highlightMatch(word, typed) {
let result = "";
let i = 0;
for (let char of word) {
if (i < typed.length && char === typed[i]) {
result += `<span class="match">${char}</span>`;
i++;
} else {
result += char;
}
}
return result;
}
// Render suggestions
function renderSuggestions(value) {
suggestionsList.innerHTML = "";
activeIndex = -1;
if (!value) {
currentSuggestions = [];
return;
}
// FUZZY search here
currentSuggestions = trie.fuzzyAutocomplete(value, 1, 10);
currentSuggestions.forEach((word, index) => {
const li = document.createElement("li");
li.innerHTML = highlightMatch(word, value);
li.addEventListener("click", () => {
input.value = word;
suggestionsList.innerHTML = "";
});
suggestionsList.appendChild(li);
});
}
// Update active selection style
function updateActiveItem() {
const items = suggestionsList.querySelectorAll("li");
items.forEach(item => item.classList.remove("active"));
if (activeIndex >= 0 && items[activeIndex]) {
items[activeIndex].classList.add("active");
}
}
// Input typing
input.addEventListener("input", () => {
const value = input.value.trim().toLowerCase();
renderSuggestions(value);
});
// Keyboard navigation
input.addEventListener("keydown", (e) => {
if (currentSuggestions.length === 0) return;
if (e.key === "ArrowDown") {
activeIndex++;
if (activeIndex >= currentSuggestions.length) activeIndex = 0;
updateActiveItem();
}
if (e.key === "ArrowUp") {
activeIndex--;
if (activeIndex < 0) activeIndex = currentSuggestions.length - 1;
updateActiveItem();
}
if (e.key === "Enter") {
if (activeIndex >= 0) {
input.value = currentSuggestions[activeIndex];
suggestionsList.innerHTML = "";
currentSuggestions = [];
}
}
if (e.key === "Escape") {
suggestionsList.innerHTML = "";
currentSuggestions = [];
}
});
</script>
</body>
</html>
Likee Dis
fuzzyAutocomplete(input, maxEdits = 1, limit = 10) {
let results = new Set();
const dfs = (node, wordSoFar, index, editsUsed) => {
if (results.size >= limit) return;
if (editsUsed > maxEdits) return;
if (index === input.length) {
if (node.isEndOfWord) {
results.add(wordSoFar);
}
for (let char in node.children) {
dfs(node.children[char], wordSoFar + char, index, editsUsed + 1);
}
return;
}
const currentChar = input[index];
// Case 1: Exact match
if (node.children[currentChar]) {
dfs(
node.children[currentChar],
wordSoFar + currentChar,
index + 1,
editsUsed
);
}
// Case 2: Substitution (wrong character)
for (let char in node.children) {
if (char !== currentChar) {
dfs(
node.children[char],
wordSoFar + char,
index + 1,
editsUsed + 1
);
}
}
// Case 3: Deletion (skip input character)
dfs(node, wordSoFar, index + 1, editsUsed + 1);
// Case 4: Insertion (extra character added)
for (let char in node.children) {
dfs(
node.children[char],
wordSoFar + char,
index,
editsUsed + 1
);
}
};
dfs(this.root, "", 0, 0);
return [...results].slice(0, limit);
}
}
Rezults

Want to know moar?
✨ Levenshtein-style Trie DFS ✨
Want to know moar?
Go forth, and Trie your best!
Data Structure - Trie
By signupskm
Data Structure - Trie
- 47