Regex Match Game — Free HTML CSS JS Snippet
Regex Match Game · Games · Plain HTML, CSS & JS · Live preview
For supporters
Source Code
Want the source code?
Play the game for free. Become a WebDevPuneet supporter to unlock the complete HTML, CSS and JavaScript source code, the exports (HTML, React, Vue, Angular, Tailwind) and Fork.
Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Regex Match Game — Live RegExp.test Grading, Positive and Negative String Sets & Too-Loose Diagnosis

The hard part of regular expressions is never writing something that matches — it is writing something that matches the right things and nothing else. A pattern that passes your three examples and quietly also matches half your production data is the classic regex bug, and it is invisible if you only ever test the positive cases. This snippet is a playable trainer built around that exact tension: every level gives a list of strings the pattern must match and a second list it must reject, and a level is only cleared when both lists are fully satisfied.
Grading against string sets, not a stored answer
There is no reference pattern anywhere in the level data. Each level is defined purely by its yes and no arrays, and grading runs the player's compiled RegExp against every string in both lists with re.test(s), requiring true for every positive and false for every negative. That means any pattern satisfying the specification passes — on the file-extension level, \.(png|jpg)$, \.(jpe?g|png)$ and other equivalent formulations are all accepted. Because the specification *is* the test set, the game teaches the habit of thinking about a regex in terms of what it accepts and rejects rather than in terms of a memorised incantation.
Compiling user input without letting it break the page
compile() wraps new RegExp(pattern) in try/catch and returns null on failure, because a half-typed pattern like [a- or (png| throws a SyntaxError on nearly every keystroke while the player is composing. Returning null lets callers distinguish three states cleanly — empty input, invalid syntax, and a usable expression — so the input can show a red border mid-typing without clearing the result marks or spilling errors into the console. The input is also length-capped in the markup, which keeps a pathological pattern from being pasted in wholesale.
Live per-string verdicts on both columns
paint() walks a list, tests each string, and applies a pass or fail class based on whether the actual result equals the *desired* result for that column — so in the left column a match is a pass, and in the right column a match is a failure. Each row also prints the raw verdict ("match" or "no") next to the string, which separates the two things a learner needs to see at once: what the regex did, and whether that was what the level wanted. Because both columns are repainted on every input event, a player watches strings flip between columns' pass states character by character as the pattern is built.
Failure messages that name the direction of the error
When a submission is not perfect, the status line compares the two column scores and reports the specific failure mode: "too strict" when every negative is correctly rejected but some positives are still unmatched, "too loose" when every positive matches but some negatives matched too, and a plain score when both columns have problems. Too-loose is the regex bug that matters most in real code, and naming it explicitly — rather than saying "wrong" — is what turns a wrong answer into a lesson about over-broad patterns.
Levels as a progression of concepts
The seven levels move through literal substrings, character classes, the + quantifier, the ^ start anchor, the \d digit shorthand, alternation with an escaped literal dot and the $ end anchor, and finally a both-ends-anchored email-shaped pattern. Each carries a one-line hint explaining the technique rather than giving the answer, and the negative lists are chosen adversarially — level four rejects "blog", "catalog" and "analogue" specifically so an unanchored log fails and the player has to discover ^.
Step by step
How to Use
- 1Read both columns before typingThe green column lists strings your pattern must match; the red column lists strings it must not. The negative list is chosen adversarially — on the "log" level it contains blog, catalog and analogue, so an unanchored pattern will visibly fail.
- 2Type a pattern between the slashesThe input sits between two literal slashes to mirror regex literal syntax. Every keystroke recompiles the pattern and retests all six strings, so you see the effect of adding a single character immediately.
- 3Watch the per-string verdictsEach row prints whether the regex matched it ("match" or "no") and turns green or red depending on whether that was the desired outcome for its column — a match in the right-hand column is a failure, not a success.
- 4Press Enter or click Test to submitA submission is only accepted when every string in both columns is correct. There is no reference answer to guess: any pattern that satisfies the specification clears the level.
- 5Read the direction of your missA failed submission tells you whether you were too strict (positives still unmatched) or too loose (negatives matched as well) with counts, which is the distinction that matters most when debugging a real regex.
- 6Use hints and work through all seven levelsShow hint explains the technique the level is about — character classes, the + quantifier, anchors, \d, alternation, escaping a literal dot — without giving the pattern away. Levels ramp from a literal substring to an anchored email-shaped expression.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
No. Each level is defined only by its list of strings that must match and strings that must not; grading runs your compiled pattern against all of them with RegExp.test() and clears the level when every verdict is correct. On the image-extension level, \.(png|jpg)$ and \.(jpe?g|png)$ both pass because both satisfy the specification.
Incomplete patterns like [a-z or (png| are invalid regex and make new RegExp() throw a SyntaxError. compile() catches that and returns null, which the UI treats as "not valid yet" — the input border turns red, the string verdicts reset to a neutral dot, and nothing is logged as an error.
It means every string in the must-match column matched, but at least one string in the must-not-match column matched as well — your pattern is over-broad. That is the most common and most dangerous regex bug in real code, which is why the game names it specifically instead of just saying the answer was wrong.
Push an object onto LEVELS with four keys: goal (the instruction), yes (an array of strings the pattern must match), no (an array it must reject), and hint (a one-line explanation of the technique). No answer key is needed. Make the no array adversarial — near-misses of the yes strings are what force precise patterns.
Yes. Keep LEVELS and the compile() helper in a plain module, hold the pattern string and level index in component state, and derive the per-string verdicts during render instead of mutating classes — the whole grading step is a pure function of (pattern, level), which maps neatly onto useMemo in React, a computed property in Vue, or a computed signal in Angular. The only imperative bits are focusing the input and the advance timeout, which belong in useEffect/onMounted/ngAfterViewInit with clearTimeout on cleanup.