Form validation
Phone numbers, postcodes, date formats and password strength — the three patterns in lessons 22–24 go straight into a front-end form; remember the anchors and leave semantic checks such as “30 February” to code.
Guide
Regular Expressions is a 25-lesson interactive course: each lesson gives you a piece of test text and a goal (“match these, do not match those” or “capture this part”), you edit the pattern in the input box, the matches and groups are highlighted live on the right, and the checker tells you item by item which goals you have met. Everything is checked by the browser's own JavaScript regular expression engine, and no text is uploaded.
Updated 2026-09-094 sources13 min read
Regular Expressions is a 25-lesson interactive course: each lesson gives you a piece of test text and a goal (“match these, do not match those” or “capture this part”), you edit the pattern in the input box, the matches and groups are highlighted live on the right, and the checker tells you item by item which goals you have met. Everything is checked by the browser's own JavaScript regular expression engine, and no text is uploaded.
The course progresses by difficulty: first the basics of finding characters — literals, the dot and escaping; then character classes and ranges, the predefined classes \d \w \s and the four quantifiers; then anchors and word boundaries, alternation, the three kinds of grouping and backreferences, greedy and lazy matching; then lookahead and lookbehind assertions and the flags; and finally three real patterns (mainland China mobile numbers, email addresses and dates) to practise on, closing with a lesson on catastrophic backtracking where you deliberately write a pattern that locks the engine up and then fix it.
It takes about 60 minutes. It suits people who have never written a regular expression, or who can copy one but not adapt it; readers who already know the basics can jump to the lookaround and traps from lesson 17 onwards.
?lesson=n that lands directly on that lesson, and “Reset progress” clears every record for the course.The course itself is above this page: pick a lesson in the contents on the left, type a pattern in the middle, and the verdict appears as you go.
Lesson 14, “capture groups”, asks you to pull the year out of 2026-09-09. Here is a record of one run:
Test text Release date: 2026-09-09, deadline: 2027-01-31
First attempt \d+
Matches 2026 09 09 2027 01 31 ← 6 matches, no capture group
Verdict ✕ only two whole dates should match ✕ group 1 should be the year
Second attempt (\d{4})-\d{2}-\d{2}
Matches 2026-09-09 2027-01-31 ← 2 matches
Group 1 2026 2027
Verdict ✓ 2 matches ✓ group 1 = 2026 / 2027 → passed
Lesson 25, “catastrophic backtracking”, works the other way round: first you write a nested quantifier such as ^(a+)+$ and test it against a run of aaaaaaaaaaaaaaaaaaaaaaaaaaaa!, and the page reports the attempt count growing exponentially; then you are asked to rewrite it as something equivalent such as ^a+$ to pass.
A regular expression describes “what a class of strings looks like” with a small set of symbols. Letters and digits mostly stand for themselves, while a dozen metacharacters (. * + ? ^ $ | ( ) [ ] { } \) carry special meaning. The engine scans the text from left to right with the pattern, tries to match at every position and reports the position and length when it succeeds. This course uses the JavaScript (ECMAScript) dialect, whose basic syntax is almost identical to Python, Java and Go; the differences cluster around lookaround, Unicode properties and some flags.
cat matches the three characters c, a and t in sequence. The dot . matches any single character except a newline, so c.t matches cat, cut and c9t. When you really want a full stop (the dot before a file extension, say) you write \. — the backslash returns a metacharacter to its literal meaning. Every metacharacter can be escaped this way; inside a character class only ] \ ^ - need it.
Square brackets list the candidates: [aeiou] matches any vowel; [a-z] and [0-9] use a hyphen for a range and can be listed side by side as [A-Za-z0-9_]; a leading ^ negates, so [^0-9] matches anything that is not a digit. Three common predefined classes are shorthand for these:
| Class | Equivalent to | Notes |
|---|---|---|
\d |
[0-9] |
Digits (not CJK numerals and not full-width digits) |
\w |
[A-Za-z0-9_] |
“Word characters” — note that CJK characters do not count |
\s |
[ \t\n\r\f\v…] |
Whitespace, including the ideographic space |
The uppercase forms \D \W \S are the negations. \w not covering CJK is the trap CJK users hit most often: to match CJK characters write [\u4e00-\u9fa5], or use \p{Script=Han} under the u flag.
A quantifier follows one atom (a single character, a character class or a group) and says how many times it repeats:
* zero or more, + one or more, ? zero or one;{n} exactly n times, {n,} at least n, {n,m} between n and m.\d{4} reads better than \d\d\d\d, and \d{11} is a phone number's length. A quantifier applies only to the one atom immediately before it: ab+ is a followed by one or more b, and repeating the whole ab needs (ab)+.
^ and $ match no characters at all, only positions: the start and end of the string (or of each line in multiline mode). Form validation should almost always use ^…$, or \d{11} will happily “match” the first 11 digits of a 12-digit number. \b is a word boundary — the position between a \w and a \W (or the ends of the string). \bcat\b matches a cat but not concatenate. Again because \w excludes CJK, \b is essentially useless in CJK text.
| means “or” and has the lowest precedence: cat|dog matches either word, and limiting the scope needs parentheses, as in gr(a|e)y. Parentheses also create a capture group: after a successful match the text inside can be read by number (match[1]) or referenced in a replacement string with $1 — one of the most productive uses of regular expressions, since turning 2026-09-09 into 09/09/2026 takes a single replace.
(?<year>\d{4}) is a named group read as groups.year, which is far less likely to shift when you edit the pattern than a numeric index. (?:…) is a non-capturing group: it groups without taking a number, which is what you want when you only need to scope a quantifier or an alternation. \1 is a backreference — it demands that whatever group 1 captured appears again here: (\w)\1 matches doubled letters such as ll and oo, and <(\w+)>.*?</\1> matches a pair of HTML tags.
Quantifiers are greedy by default: they take as much as they can and then give some back. Applied to "a" and "b", ".+" swallows everything from the first quote to the last. Adding ? after a quantifier makes it lazy: ".+?" takes as little as possible, giving two matches "a" and "b". A more precise alternative is a negated character class, "[^"]+", which does not rely on backtracking and is usually faster.
Lookaround matches a position like an anchor does, but with the condition that a pattern is (or is not) next to it:
(?=…) lookahead: what follows must be …; \d+(?=元) matches the 100 in 100元 without including the currency character in the match.(?!…) negative lookahead: what follows must not be ….(?<=…) lookbehind: what precedes must be …; (?<=¥)\d+ matches a price without the currency symbol.(?<!…) negative lookbehind.JavaScript has supported lookbehind since ES2018, and only from Safari 16.4 is it universally available; Python requires lookbehind to be fixed-length. Password strength checks are the classic use: ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$ uses three lookaheads to check “contains a digit, contains a lowercase, contains an uppercase” from the same position, and then .{8,} consumes the whole string.
Flags go outside the pattern: i ignores case; g finds every match rather than the first (the course's highlighting always uses g); m makes ^ and $ match each line; s lets . match newlines too; u turns on Unicode mode, so . matches by code point rather than UTF-16 code unit and an emoji counts as one character; y is sticky, requiring a match immediately at lastIndex.
Mainland China mobile numbers: ^1[3-9]\d{9}$ — starting with 1, second digit 3–9, eleven digits in all. Do not try to enumerate carrier prefixes; they are extended every year.
Email: a regular expression that fully implements RFC 5322 runs to thousands of characters and is not practical. A reasonable form check is ^[^\s@]+@[^\s@]+\.[^\s@]+$: exactly one @, non-empty on both sides, and at least one dot in the domain part. Real verification is a confirmation email.
Dates: ^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ constrains the month to 01–12 and the day to 01–31, but cannot know how many days February has — leave syntax to the regular expression and semantics to code.
The engines in JavaScript, Python, Java and PCRE all backtrack: when several splits are possible they try one, and on failure they come back and try the next. Nested quantifiers make the number of splits grow exponentially: faced with 30 a characters plus a !, ^(a+)+$ has to try more than 2^30 different splits before it can declare failure, freezing the browser tab for seconds or minutes. A pattern like that accepting user input is a denial-of-service attack (ReDoS).
The rules for avoiding it: do not let two adjacent quantifiers match the same characters ((a+)+, (\w+\s?)+ and (.*)* are all danger signs); replace a lazy .*? with a negated character class; and put a length limit on the input. Go and Rust use Thompson NFA / DFA engines that guarantee linear time, at the cost of not supporting backreferences or lookaround — Russ Cox's article explains the difference between the two implementations most clearly.
Matching nested structures (balanced brackets, a whole HTML document), tasks that need counting (deciding whether the numbers of opening and closing brackets are equal) and judgements that depend on context or semantics (whether a number is a legal date) are impossible or very hard with regular expressions. They are a tool for the lexical layer: finding tokens, splitting, simple validation and bulk replacement — and for those they are irreplaceable.
(?i) modifier, Python's (?P<name>), Java's \p{javaLowerCase} and the POSIX character classes [[:alpha:]] are not covered.u flag is only introduced briefly in the flags lesson; \p{…} property escapes and the set operations of the v flag are out of scope.\w matches CJK characters”: it does not. \w is ASCII letters, digits and underscore only; use [\u4e00-\u9fa5] or \p{Script=Han} (which needs the u flag) for CJK.. matches any character”: by default it does not match a newline; add the s flag or use [\s\S] to cross lines.\d{11} accepts exactly 11 digits”: without anchors it will find an 11-digit run inside a longer number; validation always needs ^…$.[A-z] is all the letters”: in ASCII there are six symbols between uppercase Z (90) and lowercase a (97) — [ \ ] ^ _ \`` — and they are matched too; write [A-Za-z]`.ab+ repeats only the b; ab{2} matches abb and not abab.> inside an attribute and comments all defeat it, so use a DOM parser.Phone numbers, postcodes, date formats and password strength — the three patterns in lessons 22–24 go straight into a front-end form; remember the anchors and leave semantic checks such as “30 February” to code.
The find-and-replace in VS Code, Sublime and the JetBrains IDEs all support regular expressions and $1 references. After the grouping lesson you can turn every 2026-09-09 into 09/09/2026 in one pass, or prefix every console.log(.
Pulling IPs, status codes and timings out of tens of thousands of log lines: a pattern like (\d+\.\d+\.\d+\.\d+).*?" (\d{3}) (\d+)ms with named groups turns into statistics in a few lines of script.
When nested or overlapping quantifiers such as (\w+\s?)+$, (.*a)+ or (a|aa)+ appear where user input is handled, ask for a rewrite or a length limit.
The check looks at the result, not the spelling: if everything that should match does, everything that should not match does not, and the capture groups agree, you pass. The reference answer is only one way to write it.
Usually a fragment that should not match was matched, or the match is one character longer than the target (punctuation swallowed, for instance). Look at which item in the result bar is ✕.
The syntax in lessons 1–18 is entirely portable to mainstream languages. In the lookaround lesson, Python requires fixed-length lookbehind and writes named groups as (?P<name>…); the flag syntax differs between languages.
In the current browser's localStorage; nothing is uploaded. Changing browser or clearing site data loses it; you can note where you got to with the “share this lesson” link.
The demo counts simulated steps and declares “catastrophic backtracking” past a threshold. Running it for real would make the whole tab unresponsive and give you no feedback at all.
The course runs entirely locally in the browser: the patterns and test text you type go only to the browser's own regular expression engine and are never sent to a server; progress is stored in local localStorage. The input is released when the page is closed.
Updated 2026-09-09
25-lesson interactive regex tutorial: literals, character classes, quantifiers, groups, lookarounds and backtracking traps, with matches highlighted as you type
Goal写一个表达式,匹配所有含有 cat 的词,不匹配其它词。
最简单的正则就是一串普通字符:cat 会在文本里寻找连续的 c、a、t。正则默认是「包含」而不是「等于」——concat 和 category 里都有 cat,所以也算匹配。字母、数字、空格、中文都是字面量,只有十几个符号有特殊含义,后面几关逐个介绍。