Accept one fact first: regex is not universal
Regex excels at clear, fixed-structure matching: log lines, config fragments, format pre-checks. It is poor at nested structures — use a real parser for HTML, JSON or source code. Parsing nested content with regex fails eventually.
Frequently used recipes
| Need | Pattern | Notes |
|---|---|---|
| Email (loose) | [\w.+-]+@[\w-]+\.[\w.-]+ | Format pre-check only; verification is sending mail |
| Mainland China mobile | 1[3-9]\d{9} | Prefixes change; do not hardcode every range |
| IPv4 (four groups) | (?:\d{1,3}\.){3}\d{1,3} | Does not check 0-255; validate numerically too |
| ISO date | \d{4}-\d{2}-\d{2} | No calendar validity; 2026-19-99 passes |
| Trim both ends | ^\s+|\s+$ | Prefer trim() in most languages |
| Collapse repeated lines | ^(.*)(?:\r?\n\1)+$ | Requires multiline mode |
Four mechanisms you must know
- Greedy vs lazy:
.*matches as much as possible;.*?as little as possible. This is the most common source of bugs; - Groups:
()captures,(?:)does not, and\1back-references what an earlier group matched; - Lookaround:
(?=...)positive and(?!...)negative lookahead assert what must or must not follow; - Anchors and flags:
^and$behave very differently with m (multiline) and s (dot matches newline).
Catastrophic backtracking (ReDoS)
Nested quantifiers like (a+)+$ make matching time grow exponentially on long non-matching input, pinning the CPU. Avoid nesting quantifiers, use possessive quantifiers or atomic groups where supported, cap the length of user input, and never let user-supplied patterns run server-side unchecked.
Practical advice
- Write "should match" and "should not match" samples before the pattern;
- Split hard validation into "regex pre-check + precise code check";
- Use named groups so extraction stays readable;
- Treat regex as code: comment it, store it as a constant, review it.
Common questions
Why did .* swallow the whole line? Greedy behaviour — use .*? or a restricted class such as [^"]*. Why does \d match full-width digits? Depends on Unicode mode in your engine; write [0-9] when you mean ASCII. Can regex check password strength? It can check length and character classes, but strength is entropy — a dedicated estimator fits better.
How to debug a pattern
Use a regex tester (or your editor's regex search) and verify in three stages: first the character class, then the quantifier, then the anchors. Testing quantifiers separately from groups shows quickly which part over-matches. Also keep "should not match" samples — testing only positive cases hides the worst problem of all, over-matching.
Related reading: Strong passwords: why length beats complexity
Keeping regex maintainable
The hard part is not writing a pattern but understanding and changing it six months later.
- Name and comment: define common patterns as named constants with comments describing what they accept and reject. Inlining expressions in business logic is the surest way to lose control.
- Split and compose: break complex patterns into testable fragments and combine them — reusable, and it isolates which piece fails.
- Pair with test cases: keep matching and non-matching samples per pattern, especially near boundaries such as one-digit input, very long strings and text with newlines.
- Prefer simpler tools: substring and prefix checks need plain string methods — faster and far more readable than a pattern.
- Mind language differences: implementations vary on assertions, anchors and escaping, so re-verify rather than copying across languages.
With these habits, regex stops being code only the author dares touch and becomes ordinary maintainable logic.