How to play
Type a regex into Your pattern, between the two slashes. Every line under the pond’s MATCH sign has to contain a match somewhere, and every line under SKIP has to contain none. It’s judged as you type: matched text lights up gold, a matched line gets a duck, a clear skip line a flower, and a skip line you catch turns red with a bug. Point at, tap or Tab to a line to see each match’s position and where every group landed.
The status line reads -- MATCHING -- with a count such as “5 of 6 lines right”, -- ERROR -- with the engine’s message for a pattern that doesn’t compile, or -- SOLVED --.
The score is the pattern’s length in characters, lower is better. The counter in the field shows it against par, the length of the game’s own answer: 6/3 is 6 against a par of 3. Par or under earns gold, up to half as long again silver, anything longer bronze.
A solve brings up Next level (or Enter), Try for fewer and Copy share link, which copies a link to your levels solved and total length. Ask the duck gives up to three free questions per level. A level opens once the one before it is solved.
What you’ll learn
Twelve levels, one idea each:
- Plain text: literals, and that matching is case-sensitive.
- Any character: the dot,
c.t. - Character classes:
gr[ae]y. - Ranges:
[0-9]and\d. - One or more:
hoo+ray. - Optional:
colou?r. - Anchors:
^for the start of a line. - Either or:
cat|dog. - Word boundaries:
\b, to findisbut notthis. - Escaping:
\$and\.for a price like$5.99. - Groups:
(ha){3}, and whyha{3}repeats only thea. - Backreferences:
\1, to catch a word typed twice.
The pars add up to 80 characters. Afterward you can write everyday search and grep patterns yourself and read a backreference when you meet one.
Good to know
- It runs your browser’s JavaScript engine. The judge is
new RegExp(pattern)with no flags. So\dis ASCII 0–9, even withu, where Python’srealso matches digits such as٣. Possessive quantifiers (a++) and atomic groups ((?>…)) are syntax errors, though PCRE and Python 3.11+ accept them. Named groups are(?<name>…), not Python’s(?P<name>…). With no flags, matching is case-sensitive and\p{L}means the textp{L}. - Every level is proven solvable: a gate runs each level’s own answer through the judge, and 17 fixtures pin down matches, groups and errors.
- A runaway pattern can’t freeze the tab. The judge runs in a Web Worker and stops a pattern still running after 0.7 seconds, such as
(\w*\w*\w*)*x. - Progress stays in your browser: your best length per level, in
localStorage. The game reopens at your first unsolved level. - Not covered yet: flags,
$as an end anchor, negated classes like[^0-9],{2,4}, lazy quantifiers, lookarounds and named groups. The last two work; no level asks for them. - RegexOne is a free written tutorial: short lessons with lines to match, skip or capture, then practice problems such as emails and log files. It covers more ground. regex101 is a tester and debugger, not a course: bring your own text, pick a flavor (PCRE2, JavaScript, Python, Go, Java, .NET, Rust and more) and it explains each token. Regex Trainer is shorter than either and plays like regex golf: the shortest pattern wins.
Questions
Is regex hard to learn?
No, the core is small: literals, the dot, classes, quantifiers, anchors, groups and alternation cover most everyday patterns. What takes longer is reading dense patterns, knowing where flavors differ, and spotting ones that backtrack badly.
What’s the difference between .* and .*? (greedy vs lazy)?
.* is greedy: it takes as much as it can and gives back only when the rest of the pattern fails. .*? is lazy: it takes as little as it can and grows only when it must.
'<a><b>'.match(/<.*>/)[0]; // '<a><b>'
'<a><b>'.match(/<.*?>/)[0]; // '<a>'
'<a><b>'.match(/<[^>]*>/)[0]; // '<a>'
Laziness only moves where the match ends. A negated class, as in the last line, is often clearer.
How do I match a whole word?
Put \b on each side: \bis\b finds is in “this is it” but not in “this” or “island”. JavaScript’s \b only knows ASCII letters, digits and _, so \bcaf\b matches inside “café”. For any language, use lookarounds with u:
/(?<![\p{L}\p{N}_])café(?![\p{L}\p{N}_])/u.test('un café noir'); // true
/(?<![\p{L}\p{N}_])café(?![\p{L}\p{N}_])/u.test('cafés'); // false
How do I practice regex?
Write lines your pattern must match and lines it must not, then make it pass both, as each level here does. Then do it on your own logs or file names in regex101 or the Regex Tester; the skip lines catch patterns that match too much.