What Regex Actually Is (and Is Not)
A regular expression (regex) is a mini-language for describing text patterns. It is not a full programming language — it cannot loop, branch on runtime values, or call functions. What it can do is match, extract, and replace text that fits a structural pattern faster than any hand-rolled parser you would write for the same job.
Regex engines are built into every mainstream language. The patterns are largely portable: a regex that validates an email in JavaScript will work almost identically in Python, Go, Java, and PHP. Small syntax differences exist (especially in lookbehind and some flags), but the core is universal.
When to use regex: validating structured input (emails, phone numbers, hex colors), extracting values from strings (version numbers, IP addresses, template tokens), finding and replacing in bulk. When not to use it: parsing HTML or deeply nested structures — use a proper parser instead.
Patterns: The Building Blocks
Every regex is built from three kinds of elements: literals, metacharacters, and character classes.
Literals match themselves exactly. The pattern cat matches the substring "cat" anywhere in the input. Case matters: it does not match "Cat" or "CAT" unless you use the case-insensitive flag.
Metacharacters have special meaning:
.— Any single character except newline^— Start of string (or start of line in multiline mode)$— End of string (or end of line in multiline mode)— Word boundary — the zero-width edge betweenwandW|— Alternation (OR):cat|dogmatches "cat" or "dog"— Escape:.matches a literal period
Character classes match one character from a set:
[aeiou]— Any vowel[a-z]— Any lowercase ASCII letter[^0-9]— Anything that is NOT a digitd— Digit (equivalent to[0-9])w— Word character: letters, digits, underscores— Whitespace: space, tab, newline, carriage returnD,W,S— Negated versions of the above
Quantifiers: How Many Times
Quantifiers attach to the element immediately before them:
*— Zero or more (greedy)+— One or more (greedy)?— Zero or one (optional){3}— Exactly 3{2,5}— Between 2 and 5{2,}— 2 or more
Greedy vs lazy: By default quantifiers are greedy — they match as much as possible. Add ? after any quantifier to make it lazy (match as little as possible). On the input <b>bold</b>, the pattern <.+> matches the entire string. The pattern <.+?> matches only <b>.
Flags
Flags modify how the engine interprets the pattern. In JavaScript they appear after the closing delimiter: /pattern/flags.
i— Case-insensitive:/hello/imatches "Hello", "HELLO", "hElLo"g— Global: find ALL matches instead of just the firstm— Multiline:^and$match start/end of each line, not just the whole strings— Dotall: makes.match newlines too (not available in all engines)u— Unicode: enables full Unicode matching (important for emoji and non-ASCII)x— Extended/verbose: ignore whitespace and allow inline comments (Python, Ruby; not standard JS)
Groups and Capturing
Parentheses () create a capturing group. The engine records what the group matched, and you can reference it in the replacement string or read it from the match result.
// JavaScript
const m = '2024-01-15'.match(/(d{4})-(d{2})-(d{2})/)
// m[1] = '2024', m[2] = '01', m[3] = '15'
Non-capturing groups (?:) group without recording the match — useful for applying a quantifier without capturing:
// Match 'color' or 'colour' without capturing the optional 'u'
/colou?r/ // works but? only optional single char
/color(?:u)?r/ // non-capturing group for multi-char alternatives
Named capture groups (?<name>) make extraction far more readable in complex patterns:
const m = '2024-01-15'.match(/(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/)
// m.groups.year = '2024', m.groups.month = '01', m.groups.day = '15'
Named groups are supported in JavaScript (ES2018+), Python, PHP, .NET, and most modern engines. They also work in replacements: in JavaScript's String.replace(), use $<name> to reference them.
Lookahead and Lookbehind
Lookarounds match a position in the string without consuming characters. They are zero-width assertions.
(?=...)— Positive lookahead: what follows must match(?!...)— Negative lookahead: what follows must NOT match(?<=...)— Positive lookbehind: what precedes must match(?<!...)— Negative lookbehind: what precedes must NOT match
// Match a number only if followed by 'px'
/d+(?=px)/ // in '14px 20em', matches '14' only
// Match 'cat' only if NOT preceded by 'wild'
/(?<!wild)cat/ // matches 'housecat' but not 'wildcat'
Lookbehind is not available in all engines — JavaScript added it in ES2018. Python supports it but requires fixed-width lookbehind in most implementations.
Language Differences to Know
- JavaScript: No
x(verbose) flag natively. Lookbehind requires Node.js 10+ / Chrome 62+. Named groups require ES2018. - Python (re module): Uses raw strings
r"d+"to avoid double-escaping.re.compile()for reuse. Lookbehind requires fixed-width pattern. - Go (regexp package): Uses RE2 syntax — no lookahead, no lookbehind, no backreferences. Designed for guaranteed linear time complexity.
- Java:
Pattern.compile()is the entry point. Backslashes must be double-escaped in Java strings:"\d+". - PHP (PCRE): Closest to Perl — the most feature-rich engine. Supports all lookarounds, named groups, and recursion.
Common Mistakes
- Catastrophic backtracking: Nested quantifiers like
(a+)+on a non-matching string can cause exponential time. Test with long non-matching inputs before deploying. - Greedy matching eating too much:
<.+>on<a>text</a>matches the whole string. Use<.+?>or<[^>]+>. - Forgetting anchors:
d+matches "42" inside "abc42def". Use^d+$if you need the entire string to be digits. - Dot not matching newlines: If your input can span multiple lines, add the
s(dotall) flag or use[sS]instead of.. - Not escaping the delimiter: In JavaScript, if your pattern contains
/, escape it as/.
Real-World Patterns
// Date: YYYY-MM-DD
/^d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]d|3[01])$/
// Hex color (with or without #)
/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
// IPv4 address (rough)
/^(d{1,3}.){3}d{1,3}$/
// Slug (URL-safe)
/^[a-z0-9]+(?:-[a-z0-9]+)*$/
// Semantic version
/^(d+).(d+).(d+)(?:-[w.]+)?$/