DEV Community

Cover image for I Built a Better Wordle Solver — Here's How It Works Under the Hood
XIAO FENG
XIAO FENG

Posted on

I Built a Better Wordle Solver — Here's How It Works Under the Hood

A Wordle solver needs to answer one question: given everything I know so far, which 5-letter words are still possible?

In a running game, you accumulate clues across multiple guesses:

  • Green 🟩 — correct letter, correct position
  • Yellow 🟨 — correct letter, wrong position
  • Gray ⬜ — letter not in the word

After 2-3 guesses, you might have constraints like:

Position 1 = S (green), R is somewhere but not position 2, A and E are excluded entirely.

That's a lot of state to track. And it has to update instantly as you type.


The Approach

Dictionary Preprocessing

Everything starts with the dictionary. SOWPODS (Scrabble International word list) contains ~267K words. I filtered it down to 12,470 five-letter words — the Wordle candidate pool.

This was a one-time preprocessing step. The list lives in memory and gets filtered on every solver query.

The Filtering Engine

The core logic is a pipeline of constraint checks:

function solveWordle(rows, excludedLetters) {
return FIVE_LETTER_WORDS.filter(word => {
for (const row of rows) {
for (let pos = 0; pos < 5; pos++) {
const letter = row.letters[pos];
const color = row.colors[pos];

    if (color === 'green' && word[pos] !== letter) return false;
    if (color === 'yellow' && (
      word[pos] === letter ||
      !word.includes(letter)
    )) return false;
    if (color === 'gray' && word.includes(letter)) return false;
  }
}

for (const ch of excludedLetters) {
  if (word.includes(ch)) return false;
}

return true;
Enter fullscreen mode Exit fullscreen mode

});
}

The simplicity is deliberate. Each constraint is an independent filter — short-circuit evaluation makes it fast. On average, this runs in under 2ms for the full 12K word list.

Multi-Row State Management

The tricky part is managing state across 5 rows:

const grid = Array(5).fill(null).map(() =>
Array(5).fill(null).map(() => ({
letter: '',
color: 'empty'
}))
);

const COLOR_CYCLE = ['empty', 'green', 'yellow', 'gray'];

function cycleColor(current) {
const idx = COLOR_CYCLE.indexOf(current);
return COLOR_CYCLE[(idx + 1) % COLOR_CYCLE.length];
}

Performance

Here's the performance profile for a typical 3-guess scenario:

  • List initialization: ~0.3ms
  • Per-constraint filter: ~0.5ms per row
  • Total solve time: < 5ms

Building the UI

The UI went through three iterations:

v1 — Server-side: Every input triggered a server round-trip. Slow and wasteful.

v2 — React-style: Too much overhead for what's essentially a filtering tool.

v3 — Vanilla JS (final): Direct DOM manipulation. No framework, no dependencies, no bloat.

CSS handles the visual feedback:

.wordle-tile.green { background: #6aaa64; color: white; }
.wordle-tile.yellow { background: #c9b458; color: white; }
.wordle-tile.gray { background: #787c7e; color: white; }

What I Learned

  1. Simplicity wins — The first version tried to be clever with bitmasks and precomputed indexes. The second version just iterates over 12K words. 10x simpler and just as fast.

  2. Client-side > server-side — For a problem this small (12K items, simple string operations), there's zero reason to involve a server. It's faster, cheaper, and works offline.

  3. CSS transitions > JavaScript animations — Tile color changes feel smooth because they're hardware-accelerated CSS transforms, not JS timer loops.

Try It

👉 (https://wordhelper.me/wordle-solver)

No signup, no ads, no tracking. Just type your clues and get answers.


This is part of a larger collection of word game tools I'm building at WordHelper.me — including a Scrabble word checker, anagram solver, crossword helper, and more. All tools are free, client-side, and built with vanilla JavaScript.

Top comments (0)