HomeExperienceSkillsProjectsBlogContact
All posts

The Magic of Tries and DFS: How I Made Glyphfall Lightning Fast

While building Glyphfall.com, I ran into a classic problem. The concept is simple: a grid full of letters where players need to find words, a bit like Boggle. But under the hood, "simple" quickly turned into a performance nightmare.


The Stumbling Block: The Brute-Force Wall

At first I thought: "How hard can it be?" You start at a letter, check the neighbors, and see if that sequence of letters exists in your word list.

But the math caught up with me. In a 4x4 or 5x5 grid, there are tens of thousands of possible paths. If you have to search through a flat list of 200,000 words for each path, your browser freezes faster than you can say "word score." I needed a way to not check after the fact whether a word existed, but to know during the search whether a path was a dead end.

The Discovery: The Trie (Prefix Tree)

The solution turned out to be a Trie. Instead of a flat array of words, I built a tree structure. If I have the words "cat", "can" and "car", I store them as branches from the letter 'c'.

Building the Trie

This is the structure I used to make my word list searchable at lightning speed:

javascript
class TrieNode {
  constructor() {
    this.children = {}; // Stores the next letters
    this.isWord = false; // Is this a complete word?
  }
}

function buildTrie(dictionary) {
  const root = new TrieNode();

  for (const word of dictionary) {
    let node = root;
    for (const char of word) {
      if (!node.children[char]) {
        node.children[char] = new TrieNode();
      }
      node = node.children[char];
    }
    node.isWord = true;
  }
  return root;
}

The Search: DFS with Pruning

With the Trie in hand, I could make my Depth-First Search (DFS) algorithm smart. Instead of wandering blindly, the DFS now walks hand in hand with the Trie through the grid.

The trick is pruning: if I follow the letters "C-Z" on my board and the Trie says no word starts with "CZ", the algorithm stops searching there immediately. This saves millions of unnecessary computations.

The Search Algorithm

Here's how the core of Glyphfall fishes words out of the grid:

javascript
function findWords(board, trie) {
  const foundWords = new Set();
  const rows = board.length;
  const cols = board[0].length;

  function dfs(r, c, node, path, visited) {
    // 1. Boundary checks & check if we've already visited this cell
    if (r < 0 || c < 0 || r >= rows || c >= cols || visited[r][c]) return;

    const char = board[r][c];
    const nextNode = node.children[char];

    // 2. PRUNING: If the letter isn't in the Trie, this path is a dead end
    if (!nextNode) return;

    // 3. Mark as visited
    visited[r][c] = true;
    const currentWord = path + char;

    // 4. Word found?
    if (nextNode.isWord) {
      foundWords.add(currentWord);
    }

    // 5. Search recursively in all 8 directions
    for (let dr = -1; dr <= 1; dr++) {
      for (let dc = -1; dc <= 1; dc++) {
        if (dr !== 0 || dc !== 0) {
          dfs(r + dr, c + dc, nextNode, currentWord, visited);
        }
      }
    }

    // 6. Backtracking: reset visited for other paths
    visited[r][c] = false;
  }

  // Start the search from every letter in the grid
  const visited = Array.from({ length: rows }, () => Array(cols).fill(false));
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      dfs(r, c, trie, "", visited);
    }
  }

  return Array.from(foundWords);
}

Eliminating Jank with Web Workers

Even with this fast algorithm, I noticed a slight stutter (jank) in the UI when the board was being generated. The CPU was so busy searching that the animations would hiccup.

The final step was moving this logic to a Web Worker. This way the heavy computation runs in a separate thread and the game experience stays smooth.

The Worker (worker.js)

javascript
import { buildTrie, findWords } from './logic.js';

self.onmessage = (e) => {
  const { board, dictionary } = e.data;
  const trie = buildTrie(dictionary);
  const results = findWords(board, trie);

  self.postMessage(results);
};

The Result

With this approach, I turned a process that took seconds (and froze the browser) into a task of just a few milliseconds.

Why this works:

MethodComplexityResult
Brute ForceExponentialBrowser crashes on 5x5 grid
Trie + DFSFiltered path< 10ms for 5x5 grid
+ Web WorkerOff-thread60 FPS during computation

Glyphfall now runs smoothly on every device. The lesson? Optimization isn't just about making code faster — it's about smartly eliminating what you don't need to do.