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.

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.

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.

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:

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

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:

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.

This is all the player sees:

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.


