Every developer has copy-pasted a regex pattern from a Stack Overflow answer without fully reading it, run it against three test cases, and shipped it. Most of the time that's fine. Then it silently rejects a valid email with a + in it, or matches something it shouldn't, and you're back here again.
This is the reference to bookmark instead — the patterns you'll actually reach for, why each one is built the way it is, and the handful of regex building blocks that let you read (and fix) a pattern instead of just trusting it. Test any of these live in Regex Tester before you ship them.
The Building Blocks, Fast
If you only remember five things, remember these — they cover the vast majority of real-world patterns:
| Token | Meaning |
|---|---|
\d \w \s | digit, word character (letter/digit/_), whitespace |
+ * ? | one-or-more, zero-or-more, zero-or-one |
{n,m} | between n and m repetitions |
^ $ | start of string, end of string |
() | | group, OR (alternation) |
[abc] [^abc] | any of a, b, c — or none of a, b, c |
(?:...) | non-capturing group (grouping without creating a backreference) |
(?=...) (?!...) | lookahead — must (or must not) follow, without consuming characters |
The single most common bug in copy-pasted regex is forgetting ^ and $ (or \A/\z in some languages) — without them, your pattern matches anywhere in the string, not the whole string. \d{5} "matches" the ZIP code field abc12345xyz because it found 12345 in the middle. Anchor it: ^\d{5}$.
Email Validation
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
This is the pattern that covers the realistic 99% case — it allows dots, plus-addressing (name+tag@gmail.com), and multi-part domains. It is not a full RFC 5322 implementation (that pattern is genuinely hundreds of characters long and still debated). For anything that actually matters — signup forms, billing — validate the format with regex, then confirm the address is real by sending a verification email. Regex can't tell you the mailbox exists.
URL Matching
^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$
Covers http:// and https://, a domain, and an optional path/query string. If you need to also match bare domains without a scheme (example.com typed with no http://), that's a materially harder problem — most production tools (like link previews) normalize the input first by prepending https:// if no scheme is present, rather than trying to regex-detect it.
Phone Numbers (US Format)
^\(?\d{3}\)?[-. ]?\d{3}[-. ]?\d{4}$
Handles (555) 123-4567, 555-123-4567, 555.123.4567, and 5551234567 — the four formats people actually type. International phone numbers are a much bigger problem (variable length, country codes, leading zeros that matter) — for those, use a dedicated library like libphonenumber rather than regex, which genuinely can't validate international numbers correctly on its own.
Strong Password Check
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$
This uses lookaheads ((?=...)) to require — without consuming — at least one lowercase letter, one uppercase letter, one digit, and one symbol, then .{8,} enforces minimum length. Each (?=...) is checked independently at the start of the string, which is why you can stack four of them.
Worth knowing: character-class requirements like this are a much weaker security signal than raw length. See How Strong Is Your Password, Really? — if you're writing this check for your own app's signup form, consider requiring length (12+) over complexity, or just generate one with Password Generator.
Extracting Hashtags or Mentions
[#@]\w+
No anchors here on purpose — you want this to match anywhere inside a longer string (a tweet, a caption), not validate the whole input. This is the core distinction between a validation pattern (anchored, matches the whole string) and an extraction pattern (unanchored, finds matches inside a larger text) — mixing the two up is a common source of "why didn't this match" confusion.
IPv4 Address
^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$
The naive version of this pattern is ^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$ — simpler, but it wrongly accepts 999.999.999.999. The version above constrains each octet to 0–255 using alternation. This is a good example of the tradeoff every regex author faces: simpler patterns are easier to read and slightly wrong; fully correct patterns are dense and need a comment explaining them.
Date Format (YYYY-MM-DD)
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
Validates the shape — 4 digits, a valid month 01–12, a valid day 01–31 — but not calendar correctness. It will happily accept 2026-02-30, which doesn't exist. Format validation is regex's job; whether the date is real is your date library's job (new Date(), date-fns, etc.) — don't try to make regex do both.
HTML Tag Stripping
<[^>]*>
Matches any <...> tag to strip it from a string. The standard warning applies and is worth repeating because it's still true: don't use regex to parse or sanitize HTML for security purposes. A malformed or adversarial tag (<img src=x onerror=alert(1)> split across nested quotes) can break a hand-rolled regex in ways a real HTML parser won't. Use this pattern for quick text-extraction tasks on trusted input, and a proper library (DOMPurify, an HTML parser) for anything user-submitted.
Greedy vs. Lazy Matching
The bug that costs people the most debugging time isn't a missing character class — it's * and + being greedy by default. Given the input <b>bold</b> and <i>italic</i>, the pattern <.*> doesn't match just <b> — it greedily matches from the first < to the last >, swallowing the whole string. Add a ? after the quantifier to make it lazy: <.*?> matches the shortest possible string, giving you <b>, then </b>, then <i>, separately.
Greedy-by-default is the single most common cause of a regex that "matches too much."
Quick FAQ
Why does my regex work in Regex Tester but fail in my code?
Usually a flavor difference — JavaScript, Python, and PCRE handle some syntax (named groups, lookbehind support, Unicode flags) slightly differently. Also check whether you need the case-insensitive (i), multiline (m), or global (g) flag set in your actual code, since a browser tool may apply flags you forgot to add in the code itself.
Is regex the right tool for validating structured data like JSON or HTML?
No — regex is good at matching patterns in flat text, not parsing nested structures. Use a real parser (JSON.parse, an HTML/XML library) for anything with nesting; regex will work until it hits an edge case it structurally can't handle.
What's the difference between . and \.?
Unescaped, . matches any character. To match a literal period (as in an email or IP address), you must escape it: \.. This is one of the most common copy-paste bugs — a pattern that "matches emails" but also accepts nameXexampleXcom.
How do I match a pattern but exclude specific cases?
Negative lookahead: (?!pattern). For example, ^(?!admin$).+$ matches anything except the exact string admin. It's a niche tool, but the one clean way to express "match this, unless it's that."
Related Free Tools
- Regex Tester — test any pattern above against real input, live, with match highlighting
- JSON Formatter — for validating structured data regex isn't built to parse
- Password Generator — generate a password that passes strong-password regex without hand-typing one
- HTML Entity Encoder — the safer alternative to regex-based HTML text handling