When this skill is activated, always start your first response with the 🧢 emoji.
Regex Mastery
Regular expressions are a compact language for describing text patterns, built into virtually every programming language and text processing tool. They power input validation, log parsing, data extraction, search-and-replace, and tokenization. Used well, a single regex can replace dozens of lines of string manipulation code. Used poorly, they become unreadable traps and can grind a server to a halt via catastrophic backtracking.
When to use this skill
Trigger this skill when the user:
- Asks to write or explain a regular expression
- Wants to validate input format (email, URL, phone number, date, credit card)
- Needs to extract data from structured or semi-structured text (logs, CSV, HTML)
- Uses regex terminology: lookahead, lookbehind, named group, capture group, backreference
- Wants to debug a pattern that isn't matching as expected
- Asks about regex flags (
i,g,m,s,u,x) - Needs to replace text using capture groups or back-references
Do NOT trigger this skill for:
- Full HTML/XML parsing (use a proper parser like DOMParser or BeautifulSoup instead)
- Complex natural language processing where ML models are a better fit
Key principles
- Readability over cleverness - A regex that nobody can maintain is worse than
a slightly longer explicit approach. Break complex patterns into commented steps or use the verbose (x) flag where supported. A named group costs nothing but pays dividends every time someone reads the pattern.
- Use named capture groups -
(?<year>\d{4})is self-documenting and immune
to positional breakage when the pattern changes. Always prefer named groups over numbered groups for any regex that will be read or maintained by humans.
- Test edge cases relentlessly - Empty string, Unicode characters, very long
input, malformed-but-close input (e.g., foo@bar for email), and adversarial input designed to trigger backtracking. A regex that passes your happy path but fails on a Unicode em-dash will cause production incidents.
- Avoid catastrophic backtracking - Nested quantifiers (
(a+)+) and overlapping
alternatives ((a|ab)+) cause exponential backtracking on non-matching input. Use atomic groups or possessive quantifiers where available, or restructure alternation so choices are mutually exclusive.
- Use the right tool - Regex is not always the answer. Parsing emails to RFC 5321
compliance requires a full parser. Parsing JSON, HTML, or XML requires a DOM/SAX parser. If a regex exceeds ~80 characters or requires >2 levels of nesting, pause and ask whether a small state machine or parser would be clearer.
Core concepts
Greedy vs lazy quantifiers - , +, ?, and {n,m} are greedy by default: they match as much as possible while still allowing the overall pattern to succeed. Add ? to make them lazy (?, +?): they match as little as possible. In <.+> matching <b>text</b>, greedy gives the whole string; lazy <.+?> gives just <b>.
Backtracking engine - Most regex engines (NFA-based: JS, Python, Java, .NET, PCRE) work by trying a path and backing up when it fails. The cost of a failed match can be exponential if quantifiers are nested and the pattern allows too many overlapping interpretations. POSIX (DFA-based) engines don't backtrack but lack lookaheads and backreferences.
Character classes - [abc] matches any one of a, b, c. [^abc] is the negation. Shorthand classes: \d (digit), \w (word char), \s (whitespace), \D, \W, \S (their negations). The . metacharacter matches any character except newline (unless the s/dotall flag is set). Always prefer \d over [0-9] for clarity, and [^\n] over . when you mean "not newline".
Anchors - ^ and $ match start/end of string (or line with the m flag). \b is a word boundary (zero-width). \A, \Z are absolute start/end of string in Python (unaffected by multiline mode). Use anchors aggressively - an unanchored pattern can match anywhere in the string, which is often not what you want.
Groups and alternation - (abc) is a capturing group; (?:abc) is non-capturing (slightly faster, doesn't pollute $1/match.groups). Named groups: (?<name>abc) in JS/Python/PCRE. Alternation a|b is left-to-right and short-circuits
- put the most common or most specific branch first. Backreferences
\1or\k<name>
match the same text captured by a group.
Common tasks
Validate an email address (basic)
A practical email regex that catches most invalid formats without attempting full RFC compliance (which would require a 6553-character pattern).
const emailRegex = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/
function isValidEmail(email) {
return emailRegex.test(email.trim())
}
// Examples
isValidEmail('user@example.com') // true
isValidEmail('user+tag@sub.co.uk') // true
isValidEmail('notanemail') // false
isValidEmail('@nodomain.com') // false
Never use regex alone as the authoritative email validator in security-sensitive
code. Always send a confirmation link. The only true validator is delivery.
Validate a URL
const urlRegex = /^https?:\/\/(?:[\w\-]+\.)+[a-zA-Z]{2,}(?::\d{1,5})?(?:\/[^\s]*)?$/
function isValidUrl(url) {
try {
new URL(url) // prefer the URL constructor in JS environments
return true
} catch {
return false
}
}
Prefer the native URL constructor in JS/Node.js over regex for URL validation.
It handles edge cases like IPv6, IDN hostnames, and percent-encoded paths correctly.
Validate a phone number (E.164 format)
// E.164: +[country code][subscriber number], 7-15 digits total
const e164Regex = /^\+[1-9]\d{6,14}$/
// North American (NANP) with flexible formatting
const nanpRegex = /^(\+1[-.\s]?)?(\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}$/
e164Regex.test('+14155552671') // true
e164Regex.test('4155552671') // false (no + prefix)
nanpRegex.test('(415) 555-2671') // true
nanpRegex.test('415.555.2671') // true
Extract data with named capture groups
Named groups make extraction code self-documenting and resilient to group reordering.
const logLineRegex = /^\[(?<timestamp>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\] (?<level>INFO|WARN|ERROR) (?<message>.+)$/m
const line = '[2026-03-14T09:41:00] ERROR Database connection refused'
const match = line.match(logLineRegex)
if (match) {
const { timestamp, level, message } = match.groups
console.log(timestamp) // '2026-03-14T09:41:00'
console.log(level) // 'ERROR'
console.log(message) // 'Database connection refused'
}
Use lookahead and lookbehind
Lookarounds are zero-width assertions - they check context without consuming characters.
// Positive lookahead: password must contain a digit
const hasDigit = /(?=.*\d)/
// Negative lookahead: word not followed by "(deprecated)"
const notDeprecated = /\bfoo\b(?!\s*\(deprecated\))/
// Positive lookbehind: price value preceded by $
const priceRegex = /(?<=\$)\d+(?:\.\d{2})?/g
'Total: $49.99 and $5.00'.match(priceRegex) // ['49.99', '5.00']
// Negative lookbehind: "port" not preceded by "trans"
const portNotTransport = /(?<!trans)port/gi
Lookbehind ((?<=...)and(?<!...)) is supported in V8 (Node.js/Chrome),
.NET, and Python 3.1+, but NOT in Safari < 16.4 or older PCRE. Check target
environment before using.
Replace with capture groups
Use $1 / $<name> in the replacement string to insert captured text.
// Reformat date from MM/DD/YYYY to YYYY-MM-DD
const date = '03/14/2026'
const reformatted = date.replace(
/^(?<month>\d{2})\/(?<day>\d{2})\/(?<year>\d{4})$/,
'$<year>-$<month>-$<day>'
)
// '2026-03-14'
// Wrap all @mentions in an anchor tag
const text = 'Hello @alice and @bob'
const linked = text.replace(/@(\w+)/g, '