Regex Match Game — Free HTML CSS JS Snippet

Regex Match Game · Games · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Levels specified purely as positive and negative string sets, with no stored reference pattern to guess
Any pattern satisfying the specification is accepted, so equivalent formulations all pass
new RegExp() compiled inside try/catch, distinguishing empty, invalid and usable pattern states
Live per-keystroke retesting of every string in both columns via RegExp.test()
Raw verdict printed next to each string alongside a pass/fail colour for the desired outcome
Too-strict vs too-loose diagnosis with counts, naming the over-broad-pattern failure mode explicitly
Adversarial negative lists that force anchors, escaping and precise quantifiers to be discovered
Seven levels covering literals, character classes, +, ^, \d, alternation, escaped dots and $

About this UI Snippet

Regex Match Game — Live RegExp.test Grading, Positive and Negative String Sets & Too-Loose Diagnosis

Screenshot of the Regex Match Game snippet rendered live

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 ^.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet into an AI assistant like Claude and ask it to add a flags row (g, i, m) with levels that only solve when a specific flag is set, which teaches the part of regex most tutorials skip. Other strong extensions: add a capture-group mode where the level specifies expected captured values rather than just match/no-match, add a "shortest pattern wins" scoring rule to discourage brute-force alternation lists, show a live visualisation of which characters in each string the regex consumed using match indices, or add a catastrophic-backtracking guard that times a test run and warns when a pattern is pathologically slow on the sample strings.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a playable regular-expression game in plain HTML, CSS, and JavaScript — no frameworks or libraries.

Requirements:
- A levels array where each level is specified ONLY by a plain-English goal, an array of strings the pattern must match, an array of strings it must not match, and a one-line hint. Do not store a reference answer — the string sets are the specification.
- An input styled between two literal slashes, where the typed pattern is compiled with new RegExp() inside a try/catch. Return a three-state result: empty, invalid syntax, or usable, so a half-typed pattern shows an invalid-input state rather than throwing.
- Re-test every string in both lists on every keystroke with RegExp.test(), and show two things per row: the raw verdict (matched or not) and whether that verdict was correct for its column — a match in the must-not-match column is a failure.
- Clear a level only when every string in both columns is correct, accepting any pattern that satisfies the specification.
- On a failed submission, diagnose the direction of the error: "too strict" when negatives are all correctly rejected but positives remain unmatched, "too loose" when all positives match but some negatives matched too, with counts.
- Make the must-not-match lists adversarial near-misses so anchors, escaping and precise quantifiers have to be discovered — for example a level whose positives all start with "log" and whose negatives include "blog" and "catalog".
- Include at least seven levels progressing through literal substrings, character classes, the + quantifier, the ^ anchor, \d, alternation with an escaped literal dot and the $ anchor, plus hint and skip buttons and a cleared-levels counter.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Read 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.
  2. 2
    Type 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.
  3. 3
    Watch 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.
  4. 4
    Press 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.
  5. 5
    Read 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.
  6. 6
    Use 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

Teaching regular expressions in a course or team workshop
Specifying levels as accept/reject sets rather than answers builds the habit that actually prevents production regex bugs — reasoning about the full input space instead of the happy path. Pair it with a trie autocomplete visualizer when moving on to how string matching is implemented underneath.
Interactive docs for a product with user-authored patterns
Log search tools, redaction rules, routing configs and validation builders all expose regex to end users. A short trainer next to the docs lets people practise against sample data before pointing a pattern at anything that matters.
Developer marketing, careers pages, and conference booth screens
A regex challenge is instantly recognisable to a technical audience and takes under a minute to play. It sits well beside other learn-by-playing snippets such as the CSS Selector Challenge Game on a developer-focused page.
Reference for safely evaluating user-supplied expressions
The compile-in-try/catch pattern with a three-state return (empty, invalid, usable) is the correct shape for any input where users type an expression the app must execute — filter builders, search syntax fields, and rule editors all need exactly this handling.
Live validation-preview pattern for form builders
Form builders that let an author attach a validation regex to a field can borrow this UI directly: sample values on one side, live match verdicts on the other, so the author sees what their rule accepts before publishing the form.
Test-first thinking demonstrated in a UI
The game is effectively a tiny test runner — a specification of expected passes and failures, re-evaluated on every change. It works nicely as a visual explanation of test-driven development for people who have not seen a test suite before.

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.