How to Solve Wordle (Without Spoiling the Fun)

How to Solve Wordle (Without Spoiling the Fun)

Colin Jones

January 19, 2022

There’s a new word game on the block called Wordle, and it’s been catching on quickly — you might even say virally. The idea is similar to the game Mastermind, but with words instead of colors.

We've used Mastermind as a toy game in some of our apprenticeships, and I was similarly curious about how we might be able to approach Wordle as an engineering problem. I started out by writing an algorithm to solve it (the Hard Way), and then looked for better ways by diving into the source code in my browser (the Easy Way). This process helped illuminate some important reminders when developing for the web, which I've included at the end of this post. I promise that it doesn't spoil the fun!

Overview: Why is everyone hooked?

I’d argue that the game’s rapid rise is due in no small part to the mystery that results from social media shares: images representing guesses, with no link back to the game. So when you come across a bunch of images like this one, from various folks you follow, you get curious and want to know more:

Wordle

Unfortunately, as Anna Cook and others have pointed out, these are a mess for folks using screen readers. I’d recommend sharing these in a different format, perhaps like the image above with explanatory alt text.

Another interesting and catchy thing about the game is that you just get one puzzle per day, so you don’t quickly lose interest due to overexposure. And everybody sees the same puzzle each day, so there’s a shared community experience. Sounds sort of like the crossword puzzle, sudoku, or Jumble in the newspaper, huh?

Overview: The Rules

If you’re like me (and plenty of others, I’m sure), there’s nothing like a good logic game to get your computer-programming senses tingling.

Like Mastermind, you start with a guess, and your opponent tells you when you have a correct component in the wrong slot, and when you have a correct component in the right slot. Two things make it a bit easier than Mastermind, though:

  1. In Wordle, the opponent tells you which letter is correct, and whether it’s in the right or wrong slot. In Mastermind, you have to figure out which color the evaluation refers to.
  2. In Wordle, any guess has to be a valid word — you can’t just string arbitrary letters together to home in on the solution like you can with the more abstract colors in Mastermind. This makes it easier to solve in one sense, because the search space gets pruned much more quickly, but it means you need a dictionary. It also opens the door to potential optimizations, like starting with the most common letter among the remaining possibilities.

Solving Wordle The Hard Way

So let’s do this. Let's develop an algorithm that can solve the Wordle.

My plan going in was simple: Grab a wordlist (/usr/share/dict/words on MacOS), filter down to only the five-letter words, collect the most commonly occurring letters, and use that to influence guesses.

Crank on it for a bit and skip to the end, and I’ve got something that always works ... eventually. It’s fast enough (there are only so many five-letter words in the language), it just doesn’t always solve within six guesses — usually it does, though!

I wouldn’t recommend staring directly into the code (it’s just a quick hack, nothing too exciting), but the basic algorithm I used looks like this (feel free to skim!):

  • Put the whole wordlist into a set of possible words.
  • Build a guess. For each position:
    • If we already know the letter at that position, use it. This is Wordle’s Hard mode, which seemed the most intuitive and tends to be the way I manually solve them anyway).
    • Otherwise, find the most common letter at that position among the remaining possible words, append it to a guess we’re building up, and filter down the remaining possible words for this guess only. We’re not sure this is the right letter yet, so we aren’t ruling out anything completely — we just want to make sure we’re building a valid word.
      • If there’s a tie for the most common letter, break the tie by picking one we haven’t already decided to use for this guess. Hopefully this maximizes our learning by using a wider variety of characters.
      • A note: the idea I started with was to optimize for letter choices that’d bisect the search space, but ultimately found it wasn’t as great a fit as I’d hoped. Bisecting the search space is typically great, but our goal of six guesses isn’t enough to get us there since we have more than 2^6 possible words. So we need something better. It’s an interesting idea though, and maybe there’s an insight I’m missing!
  • Now that we have a guess, evaluate it, learning which letters are yellow and green, and incorporate that feedback for the next guess:
    • Handle all the green letters first (we’ll see why in a moment). If a letter is green, mark that as known so that we continue to use it in future guesses, and rule out any words without this letter at this position.
    • If a letter is yellow, rule out any word that either (a) doesn’t have this letter at all, or (b) has this letter at this position (since it’d be green if it was in this position).
    • For blank letters, there’s at least one interesting edge case: usually, we want to eliminate all words with this letter. However, if we guessed a word with more than one of a given letter, and the solution only has one, we might rule out our solution! For example, if we guess rarer, and the solution is fires, we’ll have a green r in the middle, but the others will be marked blank. So we want to make sure we only rule a word out if word.count(blank_letter) >= guess.count(blank_letter). The logic here is a bit gnarly, but it works, at least for the scoring algorithm that Wordle uses. This is the case that required filling in the greens first.
  • Loop back to guessing again, until we have an all-green solution!

I’m confident there are better algorithms for this problem, and that others have already implemented them. I found out during Advent of Code last month that my algorithm skills are rusty, or maybe just underdeveloped. It’s definitely its own skill. So if you’ve got cool alternatives or optimizations to share, I’d love to hear about them (Twitter DMs are open)!

Issues

There are at least a couple of gotchas that prevent my algorithm from always winning the game. In particular, you can pretty easily get yourself into a place (like in the amazing Letterle variant) where you’ve guessed all the right characters except one, and you have no choice but to guess each of the remaining possible letters, one at a time.

Here are a few example failures from recent test runs (which I capped at 10 tries arbitrarily):

  • Was looking for 'eater'. Guesses: ['stony', 'talar', 'hater', 'pater', 'water', 'gater', 'rater', 'dater', 'mater', 'cater']
  • Was looking for 'layer'. Guesses: ['stone', 'redia', 'areal', 'lager', 'lacer', 'lawer', 'laxer', 'lamer', 'laker', 'laver']
  • Was looking for 'light'. Guesses: ['stong', 'gated', 'tight', 'wight', 'hight', 'pight', 'fight', 'kight', 'bight', 'right']

See how the last seven or eight guesses, obscure vocabulary aside, are effectively a linear search through all the possible options?

There’s no way around this that I can see, in hard mode, where you have to use the information that you already know. I suspect we could do better outside of hard mode, optimizing for the largest number of new characters to try (similar to how we can break ties mentioned earlier).

The other issue I ran into is the choice of wordlist. This makes a big difference in both speed and the required number of guesses. And it got me thinking: what wordlist does Wordle use? Certainly not my Mac’s, since (thank goodness!) websites can’t read my local filesystem. So how does it work?

Solving Wordle the Easy Way

I knew there was a chance I just wouldn’t be able to find out, but figured I’d at least take a look at the JavaScript driving the game.

Lucky for me, two word lists are right there in the minimized on-page JavaScript source code, at the time of this writing called La and Ta. Great news! So I could pretty easily grab those words, slam them into a one-word-per-line text file, and test my solver using them.

In case you’re curious, one word list seems to be for the words allowed as solutions, and the other word list is for the words that are allowed as guesses. The possible solutions list, thankfully, uses much more common words than the allowed guesses list.

But could I do better? It can be fun to poke around apps using the browser’s developer tools. Since the wordlist was in the JavaScript app, it seemed reasonable to assume that I could get everything I needed to get the solution from there. And sure enough, there were no network requests.

Minimized source code can be a bit scary to read, and sometimes playing around in the browser console can be easier. So I just started typing things and hitting <tab>, eventually got a hit on wordle, and autocomplete did (most of) the rest:

(new window.wordle.bundle.GameApp).solution
//=> "favor"

I say "most of" because window.wordle.bundle.GameApp is a class, and I needed to instantiate it to get the goods underneath.

There’s another path too: the solution is stored in LocalStorage along with some other game state:

JSON.parse(localStorage.gameState).solution
//=> "favor"

There are probably others.

But then I wondered, can we go further? How would I, say, find the solutions from last week, or for next week? I wanted to be like Old Biff in "Back to the Future 2," with Wordle solutions as my lucrative sports almanac.

Being Old Biff

I took another look through the source code, this time searching for references to La[. By diving into that list of solution words, I'm hoping to find something that will lead me to specific solutions for each puzzle. Lucky for me again, there’s just one reference:


var Ha = new Date(2021, 5, 19, 0, 0, 0, 0);

function Na(e, a) {
				var s = new Date(e),
								t = new Date(a).setHours(0, 0, 0, 0) - s.setHours(0, 0, 0, 0);
				return Math.round(t / 864e5);
}

function Da(e) {
				var a,
								s = Ga(e);
				return a = s % La.length, La[a];
}

function Ga(e) {
				return Na(Ha, e);
}

Isn’t minimized code fun to read? It’s like a mystery! What clues can be pieced together?

So the access to La (the solution words) happens at an index that’s computed essentially by the code in Na, but modded by the length of the wordlist. Now I’ll be honest with you, I played around parsing this in my mind for awhile, trying to keep track of what e referred to at various times, but it was super annoying. So I ended up pasting the implementation of Ha and Na into the console and playing around with them instead.

Long story short, 864e5 is the number of milliseconds in a day (well, most days) — 1000 * 60 * 60 * 24. So the value returned from Na (and Ga) ends up being the number of days elapsed from 2021-06-19 (yes, 06-19, that’s not a typo: JavaScript’s Date constructor has 0-indexed months and 1-indexed days) to some input date (today, it turns out, but that lives elsewhere in the code).

But if the index increases by exactly 1 every day, that means that the wordlist already tells us what’ll be coming up tomorrow, and we have our 1950-2000 Sports Almanac!

Sure enough, taking a look at the index for today gave me the answer I’d (barely) solved manually earlier in the day, and looking backwards showed me all of my previous days’ solutions.

gorge
query
drink
favor
abbey

I won’t post the future ones, because if I did how would I make any money with my time-traveling powers? Plus, nobody likes spoilers.

Also worth noting: you can pretty easily time-travel yourself by setting your system date to a different day:

Solved

What’s the point?

I still find Wordle to be a super-fun game, and the fact that I can "solve" past and future Wordles with a program’s assistance, or even just by looking at the word list, doesn’t mean anything’s wrong with it. So far I’ve successfully constrained myself to running the program on a day’s word after solving it manually — it’s too much fun!

There are a few takeaways I might offer from this fun little jaunt, though:

  1. It’s fun to code up something that’s better than you are!

    One day’s Wordle took me all six guesses, but the computer got it in three. Very similar story the next day too!

  2. The web is an awesome platform, and I think the fact that the JavaScript code is right there for us to read and understand is just really, really empowering.

    It’s great when source maps exist, for easier reading; but the lack of them doesn’t have to stop us, in a lot of cases.

  3. On a more serious note, it’s worth remembering, for those of us who work on business applications: browser code, no matter how minified or obfuscated, is not safe from prying eyes. It doesn’t take much to poke around a JavaScript app and understand how it works, or even change its behavior.

    This particular app doesn’t really use the server much at all — which I fully support and is fantastic for scalability. Imagine if Wordle was down with all these folks tweeting about it!

    Remember: API requests from client apps (whether web app, native mobile, or something else) aren’t really trustworthy, without some guarantees in place. And even if you think you’ve got some guarantees, please double-check with your teammates and/or your nearest security person before shipping it!

Happy Wordle-ing!