Regular Expressions

Regular expressions (regex) are a compact language for describing text patterns: "an email-shaped string," "a date at the start of a line," "any word repeated twice." One pattern replaces dozens of lines of string-splitting code, and the same core syntax works nearly everywhere — JavaScript, Python, grep, your editor's find-and-replace, Nginx configs, log pipelines.

Regex has a deserved reputation ("now you have two problems") that's really about misuse: patterns that try to parse HTML, validations nobody can read, and catastrophic backtracking taking servers down. Used for what it's good at — finding, validating, and transforming regular text patterns — it's one of the highest-leverage tools a developer carries.

TL;DR

Quick Example

Extracting structure from a log line, in Python and JavaScript:

Named groups ((?<name>...)) turn a cryptic pattern into self-documenting extraction — use them for anything with more than two groups.

Core Syntax

Character Classes

Inside [...], most metacharacters lose their powers — [.+*] matches literal dots, pluses, and stars. Outside, escape them: \. matches a literal dot (example\.com, not "example" + any char + "com").

Quantifiers — and Greed

Quantifiers are greedy: they grab as much as possible while still allowing a match. The classic surprise:

Often better than lazy: a negated class that can't overrun — <[^>]*> ("anything that isn't >") is clearer and faster than <.*?>.

Anchors and Boundaries

For validation, always anchor both ends. (In multiline mode, ^/$ match line starts/ends; \A/\z in Python and ^…$ without m in JS pin the whole string.)

Groups, Alternation, Backreferences

Lookarounds

Zero-width assertions — they check context without consuming it:

Flags

Verbose mode is the underused readability tool: the log-parsing pattern above is legible because it's split across lines.

The Production Pitfalls

Catastrophic Backtracking (ReDoS)

Backtracking engines (JS, Python, Java…) try alternatives when a match fails. Patterns where nested or overlapping quantifiers can split the same text many ways explode exponentially on non-matching input:

An attacker who controls the tested string can freeze your event loop with one crafted input — a real vulnerability class (ReDoS). Defenses:

Unescaped Dynamic Patterns

Interpolating raw user input into a pattern is an injection bug — same instinct as SQL injection: never mix code and untrusted data unescaped.

Using Regex Where a Parser Belongs

HTML, XML, JSON, CSV-with-quoting, nested parentheses — these are not regular languages; regex fundamentally can't track nesting. The Stack Overflow classic about parsing HTML with regex is a joke because it's true. Use the real parser (JSON.parse, an HTML parser, a CSV library) and save regex for the flat, regular fragments inside.

Over-Validating

The perfect RFC 5322 email regex is a page long and still wrong about deliverability. Pragmatic validation is ^[^\s@]+@[^\s@]+\.[^\s@]+$ plus a confirmation email — the only real proof. The same applies to URLs and phone numbers: validate shape loosely, verify semantics elsewhere.

Workflow Tips

FAQ

Is regex worth learning deeply, or just enough to Google?

The 80/20 is real: classes, quantifiers, anchors, groups, greed, and lookahead cover nearly everything you'll write. That 80% is a weekend of practice and pays off for a career — in editors, grep/log spelunking, code, and data cleaning. The dark corners (recursion, atomic groups) can stay Googleable.

Why did my regex work in JavaScript but not in Python/Go?

Dialects. Common trips: Python spells flags as re.I and multiline ^$ needs re.M; Go's RE2 rejects backreferences and lookarounds entirely; older JS lacked lookbehind; \d matches non-ASCII digits in some engines under Unicode flags and not others. Test in the target engine.

match vs search vs findall — which one?

Python: re.match anchors at the start, re.search finds the first occurrence anywhere, re.findall/finditer find all. JavaScript: str.match(re) (all matches with g), re.test(str) for booleans, str.matchAll(re) for iterating groups. Using match when you meant search (or forgetting g) is the everyday bug.

How do I match across multiple lines?

Two separate switches: s (dotall) makes . cross newlines; m (multiline) makes ^/$ match at each line. Needing both is common when carving blocks out of logs or config files.

Are LLMs good at writing regexes?

Genuinely useful for drafting and explaining patterns — but verify against your real inputs (especially adversarial ones) in a tester, and keep the ReDoS rules in mind: generated patterns favor .*? chains that a hostile input can still blow up. Trust, then test.

Related Topics

References