Most regular expressions written in anger are the same ten patterns, pasted from the same forum answers, half understood. This cheat sheet takes the other approach: a compact syntax reference, then the patterns you will actually reuse — each dissected piece by piece so you can adapt it instead of cargo-culting it. (An interactive tester is on this site’s roadmap; until it ships, every pattern below is copy-paste ready for your editor or language’s regex engine.)
The 60-second syntax reference
| Syntax | Means | Example |
|---|---|---|
. | any character except newline | a.c matches abc, a/c |
\d \w \s | digit / word char [A-Za-z0-9_] / whitespace | \d{4} = a 4-digit year |
\D \W \S | the negations of the above | \S+ = a run of non-space |
[abc] [^abc] [a-z] | character class / negated / range | [0-9a-f] = a hex digit |
* + ? | 0 or more / 1 or more / optional | colou?r = color, colour |
{n} {n,m} | exactly n / between n and m | \d{2,4} |
^ $ | start / end (of string, or line in multiline) | ^\d+$ = all digits |
(...) (?<name>...) | group (capturing) / named group | (ab)+ |
(?:...) | non-capturing group | `(?:https? |
a|b | alternation — a or b | cat|dog |
\ | escape a special character | \. = a literal dot |
*? +? | lazy versions: match as little as possible | <.+?> |
The ten patterns
1. Email — the pragmatic version
^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$
The shape is local part, an @, a domain, and a dot-TLD, allowing the characters real addresses use. Deliberately not the full RFC 5322 grammar — that pattern is hundreds of characters and still can’t tell you the mailbox exists. Validate the shape with regex, validate existence with a confirmation email. Anything stricter rejects real users (a long TLD like .museum is why the last part is {2,} and not {2,3}; plus-addressing — a +tag in the local part — is in the class for a reason).
2. URL (http/https)
^https?://[A-Za-z0-9.-]+\.[A-Za-z]{2,}(:\d+)?(/\S*)?$
Anchored, requires the scheme. Loose by design — it checks “looks like a URL”, not “is reachable”. Drop the ^…$ anchors to find URLs inside text.
3. ISO date (YYYY-MM-DD)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
The alternations do real work: months 01–12, days 01–31. It still accepts February 31st — calendar validity is not a regex job; parse the result with a date library.
4. 24-hour time (HH:MM)
^([01]\d|2[0-3]):[0-5]\d$
[01]\d covers 00–19, 2[0-3] covers 20–23; minutes are two digits 00–59.
5. IPv4 address — loose and strict
Loose (find candidates in logs): \b(?:\d{1,3}\.){3}\d{1,3}\b — also matches 999.1.1.1.
Strict (validate input): ^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ — each octet is one of 250–255, 200–249, 100–199, or 0–99. Long, but it composes from one repeated idea. (For checking whether an address is private or public, that’s classification, not pattern-matching — see public vs private IP addresses.)
6. Hex colour
^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$
#fff or #ffffff. Add |[0-9A-Fa-f]{8} inside the group if you accept an alpha channel.
7. Slug (URL-safe identifier)
^[a-z0-9]+(?:-[a-z0-9]+)*$
Lowercase words joined by single hyphens: the (?:-[a-z0-9]+)* structure forbids leading, trailing and doubled hyphens, which a naive [a-z0-9-]+ happily allows.
8. UUID
^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$
The 8-4-4-4-12 layout. Pin the version nibble ([1-5] at the start of group three) and variant ([89abAB]) only if you truly need to reject other versions.
9. Semantic version
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.\d*[a-zA-Z-][0-9a-zA-Z-]*)*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
Major.minor.patch with optional -prerelease and +build. The repeated (0|[1-9]\d*) idiom means “a number without leading zeros” — semver forbids 01.2.3.
10. International phone (E.164)
^\+[1-9]\d{6,14}$
A +, a non-zero country-code digit, and 7–15 digits total. That’s the whole E.164 shape. Anything more specific per country belongs in a phone library, not a regex — national formats are a database, not a pattern.
Traps that bite everyone once
- Greedy by default.
".*"on"a" and "b"matches"a" and "b"— the whole span. Use".*?"(lazy) or"[^"]*"(better: a class that can’t cross the delimiter). - Catastrophic backtracking. Nested quantifiers like
(a+)+$against a long almost-matching string can hang the process. If a pattern can match the same text several ways exponentially, restructure it or bound the input. - The dot is not a dot.
example.commatchesexamplexcom. Escape literal dots in domains, filenames and version numbers. - Flavours differ.
\dis ASCII-only in most engines but Unicode-aware in some modes; named groups are(?<n>…)in JavaScript/PCRE and(?P<n>…)in older Python syntax; lookahead is everywhere, lookbehind is still missing from older Safari. Test the pattern in the engine that will run it — the same lesson as the JSON world, where a strict grammar at least has the decency to be identical everywhere.