How to generate a word search with a hidden message

A word search generator with a hidden message, step by step: dictionary, word lengths, filling the grid without backtracking and success rates by grid size.

A 10×10 word search with coloured word highlights and the hidden message in coral cells

To generate a word search with a hidden message, drop random dictionary words into an empty grid until exactly as many cells stay empty as the message has letters. Then fill those cells with the message, left to right, top to bottom. On a phone this takes a few milliseconds and needs no backtracking, that is, no stepping back to earlier decisions when the generator gets stuck.

A word search is a grid of letters in which the player finds and crosses out words from a list. The hidden message is the text spelled by the letters that remain after all words are crossed out. So the generator has to build a grid that is completely full and leaves exactly the message once the words are gone. The process below is how the word searches in our crosswords and word search app (App Store, Google Play) are generated.

Grid rules

Before you start, decide what a finished grid must satisfy. In our app:

  • Words run in eight directions: horizontally, vertically and along both diagonals, each of them also backwards.
  • Two words may share at most one letter.
  • No word may pass through a cell of the hidden message.
  • The grid must be full. An empty cell is a bug.
  • The message is read left to right, top to bottom from all letters that are not crossed out.

Step 1: Prepare the dictionary

Split the dictionary into groups by word length once, at the start. Picking a random word of a given length is then a single random index. If you filter the whole dictionary for every word, generation slows down noticeably, since a single grid tries hundreds of words.

Our Slovak dictionary has 16,713 words from 3 to 21 letters long, most of them with seven or eight letters. A few practical tips:

  • Keep proper nouns and abbreviations in the dictionary. Words like HOUSTON or MAINE will then show up in the grid. Without short and "odd" words the grid can't be packed densely enough.
  • Filter profanity with a separate list that can be switched off when the player wants adult content.
  • With several languages, load only the one being played. The English dictionary has more than 414,000 words. Loading all four languages at app startup freezes a weaker phone for as long as 11 seconds.

Step 2: Pick the hidden message

The message should have at least as many letters as the grid side, and at most four more. A shorter one gets lost in the grid, a longer one needs too many holes.

In our app the hidden message is the punchline of a joke. The player crosses out words and reads the ending of the joke from the remaining letters. We build the message from the end of the joke, adding whole words from the back until its length falls into that range.

The images below use the joke "Two snakes walk into a bar. Turns out they were lizards." on a 10×10 grid. The hidden message is WERELIZARDS, 11 letters. The words in the images come from the English dictionary narrowed down to common words, so they are readable.

Why not to start with the message

The first idea is to do what you would do with a pencil: scatter the message letters over random cells in reading order, then build words around them.

Empty 10×10 grid with eleven randomly scattered message letters
Message letters scattered over the grid before any words.

That looks fine until the end. The generator picks an empty cell, tries to fit a word through it in every direction and offset, and once the grid gets dense, isolated holes remain. A single hole between two message letters can't be filled by anything, because the shortest word has three letters and no word may cross the message.

Almost full grid with three empty cells that no word can fill
After 10,000 attempts, three holes are left that no word can fill.

On a sample of 200 grids of 10×10, this approach produced a full grid in only 28 % of cases, with 1 to 2 holes left on average. Patching them afterwards is hard. So don't put the message into the grid until the words are done.

Step 3: Fill the grid until exactly N holes are left

Filling is a simple greedy algorithm: each word is placed as soon as it fits and is never removed. The generator tracks a single number: how many cells are still empty. It places a word only if that would not leave fewer empty cells than the message has letters (N).

empty = rows × cols
while empty > N and attempts < 2000:
    word = random word of the chosen length
    repeat at most 50 times:
        start = random cell, direction = random one of 8
        if the word fits the grid rules:
            new = number of empty cells the word would fill
            if empty - new >= N:
                place the word
                empty = empty - new
                continue with the next word
if empty != N: throw the grid away and start over

The first words land freely in the empty grid:

Grid with the first three words: one horizontal and two vertical
After three words. Each word has its own colour.

As the grid fills up, words start sharing letters. That is welcome, since every shared letter is one cell less to fill:

Grid with thirteen words, more than a third of the cells empty
Thirteen words, 40 empty cells.

When the number of empty cells drops to N, filling stops. Where the holes are doesn't matter. They can be scattered anywhere, because the message doesn't have to form any path through them:

Grid with twenty-two words and eleven empty cells with dashed outlines
Exactly 11 empty cells left, as many as WERELIZARDS has letters.

Step 4: Draw word lengths sensibly

Long words make a word search interesting, short ones get it finished. So draw the length from two normal distributions (you can get normally distributed random numbers from the Box-Muller transform, for example):

Probability Word length
50 % around 6 letters, standard deviation 2
30 % around 5 letters, standard deviation 1
20 % 3 letters

The length is always at least 3 and at most the grid side. Add two rules, without which the generator gets stuck at the end:

  • Force the exact length at the end. When 5 or fewer cells are missing, look for a word of exactly the missing length (at least 3). Otherwise the generator keeps trying six-letter words that no longer fit.
  • Ration three-letter words. The first three attempts at a grid may not contain any, attempts 4 to 6 may contain one, the next three two, and so on. Three-letter dictionary entries are often abbreviations like DNA or CPU and look cheap in a word search. Allow them only when there is no other way.

Step 5: Pour the message into the holes

Walk the grid left to right, top to bottom and write the next message letter into every empty cell. Since there are exactly N holes, the message fits with nothing left over.

The same grid, empty cells filled with the message letters numbered 1 to 11
Message letters in reading order. The player finds them as the last letters not crossed out.

This is all the player sees:

Finished 10×10 word search without highlights
The finished grid as the player sees it.

When a grid fails

If the generator doesn't reach exactly N empty cells within 2,000 attempts, throw the whole grid away and start over, at most 100 times. No hole patching. It is crude, but for grids up to 12×12 it is more than enough. Bigger grids need more attempts, as the table below shows.

Success rate and speed by grid size

The generator ran on 300 grids per size, with real jokes and the Slovak dictionary (Node.js 22 on a server CPU, a phone will be slower; the images use the English dictionary, the numbers the Slovak one, because it is smaller and so a stricter test). "First try" means the first round of filling ended with a full grid.

Grid First try Avg. attempts Median time Words in grid Diagonal share
8×8 78 % 1.30 1.2 ms 12.7 24 %
10×10 70 % 1.42 2.2 ms 20.4 27 %
12×12 57 % 1.73 4.7 ms 30.1 29 %
15×15 21 % 5.46 20 ms 48.7 33 %

What this means for your own generator:

  • The average word has 4.7 letters, although the target is around 6. Long words simply fail to fit into a dense grid more often.
  • About 9 to 10 % of cells are shared by two words. You don't need to tune this, it comes out of the "at most one shared letter" rule.
  • You will get fewer diagonals than you expect. The direction is drawn uniformly from eight, so diagonals should make up half of the words. In a small grid, though, a diagonal word falls off the edge much more often. On 8×8 diagonals are 24 % of words, on 15×15 already 33 %. If you want more diagonals, give them a higher weight when drawing the direction.

Tip: the same grid on every run

For store screenshots you need the exact same grid every time. In that mode, replace the usual random number generator with a simple linear congruential generator and a fixed seed (derived from the language, for example). If you keep all arithmetic within the range JavaScript numbers represent exactly, the result is identical on every device. The images in this article were generated with the same trick.

If you are solving a similar problem, generating puzzles or need a mobile app, get in touch. We also post short clips from development on YouTube. The same step-by-step approach is used in how to generate a sudoku with a unique solution. For crosswords instead of word searches, see how to generate an arrowword with a hidden message.

← More in Crosswords & Puzzles