Regex Cheat Sheet: Patterns You Actually Use
Core building blocks
.any character except newline ·\ddigit ·\wword character ·\swhitespace[a-z]range ·[^a-z]negated class*0+ ·+1+ ·?0 or 1 ·{2,5}a counted range^start ·$end ·\bword boundary(…)capture group ·(?:…)non-capturing ·(?<name>…)named
Flags
g find all matches, i ignore case, m make ^ and $ match per line, s let . match newlines, u enable full Unicode. Reusing a g regex across calls carries lastIndex state — a classic source of skipped matches.
Lookahead and lookbehind
(?=…) requires what follows, (?!…) forbids it, and (?<=…) / (?<!…) do the same backwards. Typical use: ^(?=.*\d)(?=.*[a-z]).{8,}$ to assert several conditions on one string without consuming characters.
Patterns worth keeping
- Loose email:
^[^@\s]+@[^@\s]+\.[^@\s]+$ - ISO date:
^\d{4}-\d{2}-\d{2}$ - UUID:
^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ - Slug:
^[a-z0-9]+(?:-[a-z0-9]+)*$ - Hex color:
^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
Avoid catastrophic backtracking
Nested quantifiers such as (a+)+$ can take exponential time on a crafted input and freeze a server. Prefer specific character classes over .*, and never run a user-supplied pattern on the backend without a timeout.
Greedy, lazy and possessive quantifiers
By default quantifiers are greedy: <.*> against <a>text</a> matches the entire string, because .* takes everything and then backtracks just enough to find a final >. Adding ? makes it lazy — <.*?> matches only <a>.
The better fix is usually neither: use a negated class such as <[^>]*>, which cannot overrun in the first place and runs faster because there is nothing to backtrack. As a rule, prefer "anything except the terminator" over "anything, lazily".
Groups, backreferences and replacement
Capturing groups do double duty: they extract values and they let you refer to what was matched. Inside a pattern, \1 repeats the first group — \b(\w+)\s+\1\b finds accidental doubled words. In a replacement string, $1 or $<name> inserts it.
Named groups make real code readable: (?<year>\d{4})-(?<month>\d{2}) yields match.groups.year instead of a numeric index that shifts every time someone adds a parenthesis. Use non-capturing (?:…) for grouping you do not need to extract.
Unicode-aware matching
\w means [A-Za-z0-9_] and nothing else, so it fails on é, ç, Arabic and Cyrillic. With the u flag you get property escapes: \p{L} for any letter, \p{N} for any number, \p{Script=Arabic} for a specific script.
Emoji and accented characters can also be multiple code points, so . may match half a character. The v flag and the \p{RGI_Emoji} property handle these properly in current engines; for older targets, segment with Intl.Segmenter rather than regex.
Performance and ReDoS in practice
Catastrophic backtracking happens when a pattern can match the same text in exponentially many ways. Nested quantifiers over overlapping sets are the classic trigger, and the failure mode is a request that never returns rather than an error you can see in a log:
- Dangerous:
(a+)+$,(\s*\w+)*$,(.*,)*— all exponential on a non-matching input. - Safe rewrite: replace nested quantifiers with a single one over a precise class, e.g.
[\w\s]+$. - Never compile a user-supplied pattern server-side without a timeout or a linear-time engine such as RE2.
- Anchor patterns you use for validation; an unanchored pattern scans every start position.
When not to use a regex
Regular expressions cannot parse nested structures, which rules out HTML, XML, JSON and source code — the nesting is unbounded and a regex has no memory of depth. Use a real parser; every project that tried the shortcut ended up with a fragile pattern nobody dares to touch.
The same applies to email addresses beyond a loose shape check, to CSV with quoted fields containing commas and newlines, and to natural-language dates. Regex is excellent for tokenising, validating simple shapes, and finding and replacing — not for structure.
Testing patterns before you ship them
Build a small suite of inputs before you write the pattern: three that must match, three that must not, and one deliberately hostile long string. Patterns tuned only against happy-path examples almost always over-match.
Then keep the pattern readable. Multi-line mode with comments, or simply a well-named constant next to a sentence explaining the intent, saves the next reader — usually you in six months — from reverse-engineering it character by character.
Test your pattern live
Build patterns in the Regex Tester with live match highlighting and flag toggles, then paste the working expression into your code.
Try the Regex Tester →Test JavaScript regular expressions live with match highlighting, flag support (g, i, m, s, u) and instant feedback. Free, no signup.Frequently asked questions
Should I validate email with regex?
Use a loose pattern for shape only and confirm the address with a verification email — the full RFC grammar is impractical.
Does the same regex work in Python and JavaScript?
Mostly, but escapes, named groups and Unicode behaviour differ. Test in the language you ship.
How do I match across multiple lines?
Use the s flag so . matches newlines, and the m flag so ^ and $ anchor to each line instead of the whole string.
Why does my global regex skip matches?
A regex with the g flag stores lastIndex between calls. Create a fresh regex, or reset lastIndex to 0 before reusing it.
Can regex parse HTML?
No. HTML nesting is unbounded and regular expressions cannot track depth. Use a DOM parser.