# Regex Cheatsheet
Regular expressions describe text patterns for matching, extracting, and replacing. This sheet covers the syntax shared by JavaScript, PCRE (PHP, most CLI grep flavors) and Python's re, and calls out the handful of places those engines disagree — the source of most "why doesn't this regex work" bugs.
| Syntax | Matches | Example |
|---|---|---|
| `.` | Any char except newline (unless `s` flag) | `a.c` matches `abc` |
| `\d` | Digit `[0-9]` | `\d{3}` matches `123` |
| `\D` | Non-digit | |
| `\w` | Word char `[A-Za-z0-9_]` | |
| `\W` | Non-word char | |
| `\s` | Whitespace (space, tab, newline) | |
| `\S` | Non-whitespace | |
| `[abc]` | Any of a, b, c | |
| `[^abc]` | Not a, b, or c | |
| `[a-z]` | Range a to z | |
| `\b` | Word boundary (zero-width) | `\bcat\b` won't match `catalog` |
| `\B` | Not a word boundary |
| Syntax | Meaning |
|---|---|
| `^` | Start of string (or line, with `m` flag) |
| `$` | End of string (or line, with `m` flag) |
| `*` | 0 or more (greedy) |
| `+` | 1 or more (greedy) |
| `?` | 0 or 1 (greedy); also makes a quantifier lazy: `*?`, `+?` |
| `{n}` | Exactly n |
| `{n,}` | n or more |
| `{n,m}` | Between n and m |
| Syntax | Meaning |
|---|---|
| `(...)` | Capturing group |
| `(?:...)` | Non-capturing group |
| `(?<name>...)` | Named capturing group |
| `\1`, `\k<name>` | Backreference to group 1 / named group |
| `(?=...)` | Positive lookahead |
| `(?!...)` | Negative lookahead |
| `(?<=...)` | Positive lookbehind |
| `(?<!...)` | Negative lookbehind |
| `i` | Case-insensitive |
| `g` | Global (JS: find all matches, keeps `lastIndex` state) |
| `m` | Multiline (`^`/`$` match at line breaks) |
| `s` | Dot matches newline too (JS/PCRE `DOTALL`) |
| `u` | Unicode mode (JS: required for `\p{...}`, surrogate-pair-safe) |
| `y` | Sticky — match only at `lastIndex`, no scanning ahead (JS only) |
const m = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/.exec('2026-09-29');
m.groups.year; // "2026"
const re = /\d+/g;
let m;
while ((m = re.exec('a1 b22 c333')) !== null) {
console.log(m[0], m.index);
}
// Forgetting the `g` flag here means re.exec() always returns the
// first match and re.lastIndex never advances -- infinite loop.
'2026-09-29'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
// -> "09/29/2026"
/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test('user@example.com'); // true
This is a loose email check for form UX, not RFC 5322 validation — never reject a real address just because it doesn't match a regex.
// At least 8 chars, one digit, one uppercase — order-independent
/^(?=.*[A-Z])(?=.*\d).{8,}$/.test('Passw0rd'); // true
. doesn't match newlines by default: a pattern like /start.end/ silently fails across multi-line input. Add the s (dotAll) flag, or use [\s\S] which matches everything including \n in every engine./<.>/ against <a><b> grabs the whole string, not just <a>. Use the lazy form /<.?>/ or a negated class /<[^>]*>/.^/$ mean start/end of string, not line, unless you add m:* without the multiline flag, ^ only matches position 0 even in a string with embedded \n.g-flagged regexes are stateful: a global regex remembers lastIndex between calls to .exec()/.test() on the same object. Reusing one across unrelated inputs produces skipped or missed matches — create a fresh regex or reset lastIndex = 0.+, ++), atomic groups ((?>...)), and recursion ((?R)) are valid in PHP's preg_ (PCRE) but throw a SyntaxError in JavaScript, in every mode.\K (PCRE's "reset match start") is worse than a SyntaxError in plain JS regexes — it's silently wrong: without the u/v flag, JS treats \K as an identity escape for a literal K, so /foo\Kbar/.test('fooKbar') is true and .test('foobar') is false — no error, just a pattern that quietly means something else. It only throws (SyntaxError: Invalid escape) once you add the u or v flag, which rejects unrecognized letter escapes. Conversely, JS's \p{Emoji} and other Unicode property escapes need the u flag; PCRE needs /u too but supports a broader property set out of the box.