One stubborn slot can stall a whole crossword. This free Crossword Solver attacks it the direct way: type the letters you already have, mark every unknown square with ?, and it searches our 359,039-word list for every word that fits the pattern exactly — so a slot like c?oss?ord collapses to a single answer. It runs entirely in your browser; nothing you type is uploaded.
How the Crossword Solver works
The solver treats your pattern as a row of squares. A letter square is fixed: only words with that exact letter in that exact position survive. A ? square (a full stop works too) is open: any letter may sit there. A word is returned only if it matches every fixed square and has exactly the pattern’s length, so the pattern is the word length. If you set the length menu to a longer word, the pattern is extended with unknown squares on the right — handy when you know a word’s opening letters and its length but nothing else. Results come back alphabetically, because with crosswords the question is never “what scores most” but “what fits”, and an alphabetical list is the fastest to scan against your crossing letters. Every word in the list, from 2 to 15 letters, is tested on each search.
How to use it
Work the crossing words first: every letter a crossing answer gives you is a fixed square, and fixed squares are what make this tool powerful. Type the pattern — for example cr?ss — press Solve, and check the candidates against the clue and the crossings you have not solved yet. If the list is long, do not guess: go and solve one more crossing word, add its letter, and solve again. Two or three fixed letters usually shrink a slot from hundreds of candidates to a handful.
A worked example
The pattern cr?ss returns exactly four words: crass, cress, criss and cross — a list short enough to test against any clue in seconds. Loosen the pattern to c?t and you get seven: cat, cit, cot, cpt, cst, cut and cwt. Tighten it to c?oss?ord and only one word in the entire 359,039-word list fits: crossword itself. That spread is the tool in miniature — the more squares you fix, the faster the answer appears.
Crossword tactics this supports
Crossing letters are gold: a single confirmed letter in the middle of a word eliminates far more candidates than one at either end, because English has strong habits about which letters sit where. Vowel positions matter too — most English words alternate consonants and vowels loosely, so if your pattern’s unknown squares would all have to be consonants, suspect a fixed letter is wrong. And when a pattern returns hundreds of words — a bare ????? matches all 15,921 five-letter words — the honest move is to leave the slot and harvest more crossings elsewhere on the grid.
Limitations worth knowing
Our word list is a broad English list, wider than the dictionaries crossword editors draw from, so a few entries are abbreviations or rarities (you saw cpt, cst and cwt above) that a published crossword would rarely use. Treat short, odd-looking candidates with suspicion and let the clue decide. The solver also knows nothing about clues: it cannot weigh meanings, themes or wordplay, and it handles one slot at a time rather than a whole grid. Patterns are limited to 15 squares, and very loose patterns show the first 2,000 matches alphabetically with a note, rather than freezing your browser.
Crossword Solver – frequently asked questions
Is this crossword solver free?
Yes, completely — no sign-up, no limit on searches, and it runs in your browser against a word list stored on this site.
Should I use ? or a full stop for unknown squares?
Either. Both are treated as an open square that any letter can fill; known letters are matched exactly.
Why did my pattern return hundreds of words?
Because that is genuinely how many words fit. A pattern fixes only the letters you give it; add one more confirmed crossing letter and the list usually collapses. A pattern with no fixed letters at all returns every word of that length.
Does it unscramble letters too?
No — it matches letters in fixed positions. If you have loose letters and want every word they can form, use the Word Unscrambler instead.
More word tools and lists
Browse the full collection on our Word Finder & Word Tools hub. Closest relatives: the Word Unscrambler for loose racks of letters, the Word Solver when a board pattern meets a rack of tiles, and the Word Finder for filtered searches by length and letter strings.
Embed this tool on your website — free
Copy the code below to add this working tool to your own page. A small credit link stays with it — that’s all we ask.
function ilmScoreWord(w, VAL) {
var s = 0;
for (var i = 0; i < w.length; i++) s += VAL[w[i]] || 0;
return s;
}
function ilmRackCounts(rack) {
var counts = {}, wild = 0;
rack = (rack || ”).toLowerCase();
for (var i = 0; i < rack.length; i++) {
var ch = rack[i];
if (ch === ‘?’ || ch === ‘*’) wild++;
else if (ch >= ‘a’ && ch <= ‘z’) counts[ch] = (counts[ch] || 0) + 1;
}
return { counts: counts, wild: wild };
}
// Rack solver: every word buildable from the rack (subset of tiles, ? = blank,
// blank-covered letters score 0), scored with VAL. opts may carry starts,
// ends, contains (lowercase strings) and exactLen (0 = any).
// Returns [{w, s}] sorted length desc, score desc, A-Z.
function ilmRackSolve(rack, words, VAL, opts) {
opts = opts || {};
var rc = ilmRackCounts(rack), counts = rc.counts, wild = rc.wild;
var total = wild;
for (var k0 in counts) total += counts[k0];
var starts = (opts.starts || ”).toLowerCase();
var ends = (opts.ends || ”).toLowerCase();
var contains = (opts.contains || ”).toLowerCase();
var exactLen = opts.exactLen || 0;
var out = [];
for (var j = 0; j < words.length; j++) {
var w = words[j];
if (w.length < 2 || w.length > total) continue;
if (exactLen && w.length !== exactLen) continue;
if (starts && w.indexOf(starts) !== 0) continue;
if (ends && w.slice(w.length – ends.length) !== ends) continue;
if (contains && w.indexOf(contains) === -1) continue;
var need = {};
for (var k = 0; k < w.length; k++) { var c = w[k]; need[c] = (need[c] || 0) + 1; }
var ok = true, deficit = 0, score = 0;
for (var L in need) {
var have = counts[L] || 0;
if (need[L] <= have) score += VAL[L] * need[L];
else {
deficit += need[L] – have;
score += VAL[L] * have;
if (deficit > wild) { ok = false; break; }
}
}
if (ok) out.push({ w: w, s: score });
}
out.sort(function (a, b) {
return b.w.length – a.w.length || b.s – a.s || (a.w < b.w ? -1 : a.w > b.w ? 1 : 0);
});
return out;
}
// Crossword pattern solver: pattern is letters plus ? or . for unknown
// squares. If len is given and longer than the pattern, the pattern is
// padded with ? on the right; if the pattern is longer than len, no match.
// Returns matching words sorted A-Z.
function ilmCrossword(pattern, words, len) {
pattern = (pattern || ”).toLowerCase().replace(/\./g, ‘?’);
var effLen = len || pattern.length;
if (pattern.length > effLen) return [];
var pat = pattern;
while (pat.length < effLen) pat += ‘?’;
var out = [];
for (var j = 0; j < words.length; j++) {
var w = words[j];
if (w.length !== effLen) continue;
var ok = true;
for (var i = 0; i < effLen; i++) {
var p = pat[i];
if (p !== ‘?’ && p !== w[i]) { ok = false; break; }
}
if (ok) out.push(w);
}
out.sort();
return out;
}
// Pattern + rack solver (Word Solver): the word must match the pattern
// positionally; letters on fixed squares are already on the board (free),
// letters on ? squares must come from the rack (? in the rack = blank tile,
// blank-covered letters score 0). Returns [{w, s}] score desc, A-Z.
function ilmPatternRackSolve(rack, pattern, words, VAL) {
pattern = (pattern || ”).toLowerCase().replace(/\./g, ‘?’);
var rc = ilmRackCounts(rack), counts = rc.counts, wild = rc.wild;
var out = [];
for (var j = 0; j < words.length; j++) {
var w = words[j];
if (w.length !== pattern.length) continue;
var ok = true, score = 0;
var needFromRack = {};
for (var i = 0; i < w.length; i++) {
var p = pattern[i];
if (p >= ‘a’ && p <= ‘z’) {
if (p !== w[i]) { ok = false; break; }
score += VAL[w[i]];
} else {
needFromRack[w[i]] = (needFromRack[w[i]] || 0) + 1;
}
}
if (!ok) continue;
var deficit = 0;
for (var L in needFromRack) {
var have = counts[L] || 0;
if (needFromRack[L] <= have) score += VAL[L] * needFromRack[L];
else {
deficit += needFromRack[L] – have;
score += VAL[L] * have;
if (deficit > wild) { ok = false; break; }
}
}
if (ok) out.push({ w: w, s: score });
}
out.sort(function (a, b) {
return b.s – a.s || (a.w < b.w ? -1 : a.w > b.w ? 1 : 0);
});
return out;
}
// Letter filter (Words With Letters): include = letters that must ALL be in
// the word (multiset: "ee" means at least two e’s); exclude = letters that
// must NOT appear. Optional exactLen, starts, ends. Returns words A-Z.
function ilmFilterWords(words, f) {
f = f || {};
var inc = (f.include || ”).toLowerCase();
var exc = (f.exclude || ”).toLowerCase();
var starts = (f.starts || ”).toLowerCase();
var ends = (f.ends || ”).toLowerCase();
var exactLen = f.exactLen || 0;
var incNeed = {};
for (var i = 0; i < inc.length; i++) {
var c0 = inc[i];
if (c0 >= ‘a’ && c0 <= ‘z’) incNeed[c0] = (incNeed[c0] || 0) + 1;
}
var excSet = {};
for (var e = 0; e < exc.length; e++) {
var c1 = exc[e];
if (c1 >= ‘a’ && c1 <= ‘z’) excSet[c1] = true;
}
var out = [];
for (var j = 0; j < words.length; j++) {
var w = words[j];
if (exactLen && w.length !== exactLen) continue;
if (starts && w.indexOf(starts) !== 0) continue;
if (ends && w.slice(w.length – ends.length) !== ends) continue;
var ok = true;
for (var x = 0; x < w.length; x++) if (excSet[w[x]]) { ok = false; break; }
if (!ok) continue;
var wc = {};
for (var y = 0; y < w.length; y++) wc[w[y]] = (wc[w[y]] || 0) + 1;
for (var L in incNeed) if ((wc[L] || 0) < incNeed[L]) { ok = false; break; }
if (ok) out.push(w);
}
out.sort();
return out;
}
// Random picker: opts {count, len (0 = any 2-15), startsWith}. rng() must
// return [0,1). Samples without replacement.
function ilmRandomWords(words, opts, rng) {
opts = opts || {};
var len = opts.len || 0;
var sw = (opts.startsWith || ”).toLowerCase();
var pool = [];
for (var j = 0; j < words.length; j++) {
var w = words[j];
if (len && w.length !== len) continue;
if (sw && w.indexOf(sw) !== 0) continue;
pool.push(w);
}
var count = Math.min(opts.count || 5, pool.length);
var picked = [];
for (var n = 0; n < count; n++) {
var idx = Math.floor(rng() * pool.length);
picked.push(pool[idx]);
pool.splice(idx, 1);
}
return picked;
}
// Scramble: Fisher-Yates shuffle of the word’s letters, retried until the
// result differs from the original (words made of one repeated letter are
// returned unchanged — none are in the game list).
function ilmScrambleWord(word, rng) {
var letters = word.split(”);
var distinct = {};
for (var d = 0; d < letters.length; d++) distinct[letters[d]] = true;
var nDistinct = 0;
for (var k in distinct) nDistinct++;
if (nDistinct < 2) return word;
for (var attempt = 0; attempt < 200; attempt++) {
var a = letters.slice();
for (var i = a.length – 1; i > 0; i–) {
var j = Math.floor(rng() * (i + 1));
var tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}
var s = a.join(”);
if (s !== word) return s;
}
return letters.slice().reverse().join(”);
}
// ~150 common English words for the Word Scramble game (4-8 letters).
var ILM_GAME_WORDS = [‘apple’,’banana’,’orange’,’garden’,’planet’,’silver’,’market’,’window’,’summer’,’winter’,’spring’,’autumn’,’bridge’,’castle’,’dragon’,’friend’,’school’,’pencil’,’paper’,’river’,’ocean’,’forest’,’jungle’,’desert’,’island’,’mountain’,’valley’,’village’,’kitchen’,’bedroom’,’picture’,’camera’,’guitar’,’piano’,’violin’,’trumpet’,’basket’,’bottle’,’candle’,’mirror’,’button’,’pocket’,’ticket’,’travel’,’journey’,’holiday’,’picnic’,’breakfast’,’dinner’,’supper’,’cheese’,’butter’,’coffee’,’sugar’,’pepper’,’lemon’,’melon’,’grape’,’peach’,’mango’,’rabbit’,’turtle’,’monkey’,’tiger’,’zebra’,’panda’,’koala’,’eagle’,’shark’,’whale’,’snake’,’horse’,’sheep’,’chicken’,’spider’,’flower’,’garden’,’hammer’,’ladder’,’rocket’,’planet’,’comet’,’meteor’,’galaxy’,’nebula’,’tunnel’,’harbor’,’anchor’,’sailor’,’pirate’,’treasure’,’diamond’,’golden’,’copper’,’iron’,’steel’,’wooden’,’cotton’,’leather’,’plastic’,’magnet’,’battery’,’engine’,’wheel’,’brake’,’helmet’,’jacket’,’gloves’,’scarf’,’boots’,’socks’,’shirt’,’dress’,’skirt’,’watch’,’clock’,’phone’,’tablet’,’laptop’,’screen’,’keyboard’,’mouse’,’printer’,’speaker’,’dancer’,’singer’,’farmer’,’doctor’,’nurse’,’teacher’,’writer’,’artist’,’baker’,’driver’,’hunter’,’fisher’,’sailor’,’soldier’,’captain’,’general’,’king’,’queen’,’prince’,’knight’,’wizard’,’witch’,’giant’,’fairy’,’elf’,’dwarf’,’goblin’,’troll’,’phoenix’,’unicorn’,’mermaid’,’thunder’,’lightning’,’rainbow’,’snowflake’,’raindrop’,’sunshine’,’moonlight’,’starlight’,’breeze’,’storm’,’cloud’,’fog’,’frost’,’ice’,’snow’,’rain’,’wind’,’wave’,’sand’,’stone’,’rock’,’cliff’,’cave’,’lake’,’pond’,’stream’,’waterfall’,’meadow’,’prairie’,’savanna’,’tundra’,’volcano’,’glacier’,’canyon’,’oasis’,’lagoon’,’coral’,’pearl’,’shell’,’feather’,’wing’,’claw’,’horn’,’tail’,’mane’];
ILM_GAME_WORDS = ILM_GAME_WORDS.filter(function (w, i) { return ILM_GAME_WORDS.indexOf(w) === i; });
if (typeof module !== ‘undefined’) {
module.exports = { ILM_SCRABBLE: ILM_SCRABBLE, ILM_WWF: ILM_WWF, ILM_CHUMS: ILM_CHUMS,
ilmScoreWord: ilmScoreWord, ilmRackSolve: ilmRackSolve, ilmCrossword: ilmCrossword,
ilmPatternRackSolve: ilmPatternRackSolve, ilmFilterWords: ilmFilterWords,
ilmRandomWords: ilmRandomWords, ilmScrambleWord: ilmScrambleWord, ILM_GAME_WORDS: ILM_GAME_WORDS };
}
var WORDLIST_URL = ‘https://ilmhunt.com/wp-content/uploads/2026/10/wordlist.txt’;
var WORDS = null, LOADING = false;
function sendH(){ try { parent.postMessage({wpcId: 5211, wpcH: document.body.scrollHeight + 16}, ‘*’); } catch (e) {} }
window.addEventListener(‘load’, sendH);
function st(msg){ document.getElementById(‘status’).textContent = msg; }
function ensureWords(cb){
if (WORDS) { cb(); return; }
if (LOADING) { return; }
LOADING = true; st(‘Loading the word list (359,039 words)…’);
fetch(WORDLIST_URL).then(function(r){ return r.text(); }).then(function(t){
WORDS = t.split(/\r?\n/).filter(function(x){ return x.length > 0; });
st(‘Word list ready — ‘ + WORDS.length.toLocaleString(‘en-US’) + ‘ words.’);
cb();
}).catch(function(){ LOADING = false; st(‘Could not load the word list. Check your connection and press Solve again.’); });
}
function render(list){
var box = document.getElementById(‘results’);
if (!list.length) { box.innerHTML = ‘<p class="none">No word fits that pattern. Check the letters you are sure of — one wrong fixed letter hides every answer.</p>’; sendH(); return; }
var cap = 2000, shown = list.slice(0, cap);
var html = ‘<div class="grp"><h3>’ + shown[0].length + ‘ letters — ‘ + list.length.toLocaleString(‘en-US’) + ‘ word’ + (list.length === 1 ? ” : ‘s’) + ‘</h3>’;
shown.forEach(function(w){ html += ‘<span class="chip">’ + w + ‘</span>’; });
html += ‘</div>’;
if (list.length > cap) html += ‘<p class="none">Showing the first ‘ + cap.toLocaleString(‘en-US’) + ‘ alphabetically — add more known letters to narrow it down.</p>’;
box.innerHTML = html;
st(list.length.toLocaleString(‘en-US’) + ‘ matching word’ + (list.length === 1 ? ” : ‘s’) + ‘ found.’);
sendH();
}
function run(){
ensureWords(function(){
var pat = document.getElementById(‘pattern’).value.replace(/[^a-zA-Z?.]/g, ”).toLowerCase();
if (pat.replace(/[?.]/g, ”).length < 1 || pat.length < 2) { st(‘Enter a pattern of at least 2 squares, with at least one known letter.’); return; }
var len = parseInt(document.getElementById(‘len’).value, 10);
render(ilmCrossword(pat, WORDS, len));
});
}
document.getElementById(‘go’).addEventListener(‘click’, run);
document.getElementById(‘pattern’).addEventListener(‘keydown’, function(e){ if (e.key === ‘Enter’) run(); });
</script>
</body>
</html>
" style="width:100%;height:540px;border:0;display:block;overflow:hidden;" scrolling="no"></iframe></div>
<p><a href="https://ilmhunt.com/crossword-solver/" target="_blank" rel="noopener">Crossword Solver — Find Words That Fit Your Pattern</a> by <a href="https://ilmhunt.com/" target="_blank" rel="noopener">IlmHunt</a> — free online tools</p>
