Regex Patterns Every Developer Should Know
- Regex Patterns Every Developer Should Know
- The Building Blocks: A Quick Refresher
- Understanding Regex Flags
- g — Global
- i — Case-insensitive
- m — Multiline
- s — Dotall (Single-line)
- u — Unicode
- d — Indices (ES2022)
- Pattern 1: Email Address Validation
- Pattern 2: URL
- Pattern 3: Phone Numbers
- Pattern 4: Date Formats
- Pattern 5: Hex Color Codes
- Pattern 6: Strong Password with Lookaheads
- Pattern 7: Capturing Groups and Backreferences
- Pattern 8: Non-Greedy Matching
- Pattern 9: URL Slug Generation
- Pattern 10: Whitespace Cleanup
- Pattern 11: IPv4 Address
- Pattern 12: Credit Card Number (Luhn-Ignorant Format Check)
- Pattern 13: Extract All Links from HTML
- Pattern 14: Find TODOs and FIXMEs in Code
- Python version (hash comments)
- Catastrophic Backtracking: The Silent Performance Killer
- Practical Testing Workflow
- Quick Reference: Complete Pattern Cheatsheet
- Closing Thoughts
Regex Patterns Every Developer Should Know
Regular expressions are one of those tools that look intimidating until the moment they click — and then you start seeing them everywhere. A regex that takes ten seconds to write can replace thirty lines of string-parsing logic. But they're also easy to write badly: a poorly constructed pattern can fail silently on edge cases, or worse, grind your server to a halt with catastrophic backtracking.
This guide is a practical cookbook. Each pattern comes with an explanation of what it matches, why it's structured the way it is, and what edge cases to watch for. Test and refine any of these in the Regex Tester before putting them into production code.
The Building Blocks: A Quick Refresher
Before the patterns, a fast reference on syntax that appears throughout:
| Syntax | Meaning |
|---|---|
. |
Any character except newline |
\d |
Digit (0–9) |
\w |
Word character (a–z, A–Z, 0–9, _) |
\s |
Whitespace (space, tab, newline) |
\D, \W, \S |
Negated versions of above |
^ |
Start of string (or line in multiline mode) |
$ |
End of string (or line in multiline mode) |
* |
Zero or more (greedy) |
+ |
One or more (greedy) |
? |
Zero or one; also makes quantifiers non-greedy |
{n,m} |
Between n and m repetitions |
[abc] |
Character class (a, b, or c) |
[^abc] |
Negated character class |
(...) |
Capturing group |
(?:...) |
Non-capturing group |
| ` | ` |
\b |
Word boundary |
\1, \2 |
Backreference to capturing group 1, 2 |
(?=...) |
Positive lookahead |
(?!...) |
Negative lookahead |
(?<=...) |
Positive lookbehind |
(?<!...) |
Negative lookbehind |
Understanding Regex Flags
Flags (also called modifiers) change how the entire pattern behaves. In JavaScript they're appended after the closing slash: /pattern/flags.
`g` — Global
Without g, a regex stops after the first match. With g, it finds all matches. Used with String.matchAll(), String.replace() for full replacements, and RegExp.exec() in a loop.
"cat bat sat".match(/[a-z]at/g); // ["cat", "bat", "sat"]`i` — Case-insensitive
Makes the pattern ignore case. /hello/i matches hello, Hello, HELLO, and any mixed case.
/hello/i.test("Hello World"); // true`m` — Multiline
Changes ^ and $ to match the start and end of each line rather than the whole string. Essential when parsing text files or multi-line blocks.
"line1\nline2".match(/^\w+/gm); // ["line1", "line2"]`s` — Dotall (Single-line)
Makes . match newline characters as well. Without s, . never matches \n. This flag is critical when you need to match content that spans multiple lines.
/start.*end/s.test("start\nmiddle\nend"); // true`u` — Unicode
Enables full Unicode matching. Required for patterns that use \p{...} Unicode property escapes or need to correctly handle characters outside the Basic Multilingual Plane (like emoji).
/\p{Emoji}/u.test("🎉"); // true`d` — Indices (ES2022)
Causes match results to include indices — an array of start/end positions for each captured group. Useful for code editors and linters.
Pattern 1: Email Address Validation
/^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/What it matches: A string that looks like a valid email address.
Breakdown:
^[a-zA-Z0-9._%+\-]+— The local part (before @): letters, digits, and the special characters.,_,%,+,-. One or more.@— Literal at sign.[a-zA-Z0-9.\-]+— The domain name: letters, digits, dots, hyphens.\.— Literal dot before the TLD.[a-zA-Z]{2,}$— Top-level domain: at least two letters.
Caveats: The actual email specification (RFC 5321/5322) is extraordinarily complex. This pattern accepts the vast majority of real-world addresses while rejecting obvious garbage. It will reject technically valid but unusual addresses like "user name"@example.com. For serious production use, send a confirmation email rather than relying solely on regex.
const emailRegex = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test("user@example.com")); // true
console.log(emailRegex.test("user+tag@sub.domain.io")); // true
console.log(emailRegex.test("not-an-email")); // falsePattern 2: URL
/https?:\/\/[^\s/$.?#].[^\s]*/A more robust version:
/^(https?:\/\/)([\w\-]+\.)+[\w\-]+(\/[\w\-./?%&=+#]*)?$/iWhat it matches: HTTP and HTTPS URLs.
Breakdown:
^(https?:\/\/)— Protocol:http://orhttps://.([\w\-]+\.)+— One or more domain segments each followed by a dot (e.g.,www.,sub.example.).[\w\-]+— Final domain segment (e.g.,com,io,co).(\/[\w\-./?%&=+#]*)?$— Optional path, query string, and fragment.
Tip: URL parsing is another area where regex is good for a quick sanity check, but a proper URL parser (new URL(str) in JavaScript) is more reliable for critical logic.
Pattern 3: Phone Numbers
Phone number formats vary wildly by country, so the goal here is usually to accept common formats rather than validate international correctness.
US phone numbers (flexible formatting):
/^[+]?[(]?[0-9]{3}[)]?[\s.\-]?[0-9]{3}[\s.\-]?[0-9]{4}$/What it matches: 555-867-5309, (555) 867-5309, 555.867.5309, +15558675309
Breakdown:
[+]?— Optional leading plus.[(]?[0-9]{3}[)]?— Area code, optionally wrapped in parentheses.[\s.\-]?— Optional separator: space, dot, or hyphen.[0-9]{3}— Exchange code.[\s.\-]?— Optional separator again.[0-9]{4}$— Subscriber number.
Strip-then-validate approach: For a cleaner solution, strip all non-digit characters first, then check the digit count:
const digits = phone.replace(/\D/g, "");
const isValid = digits.length === 10 || (digits.length === 11 && digits[0] === "1");Pattern 4: Date Formats
YYYY-MM-DD (ISO 8601):
/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/What it matches: 2026-06-04, 1999-12-31
Breakdown:
(\d{4})— Four-digit year, captured.(0[1-9]|1[0-2])— Month 01–12.(0[1-9]|[12]\d|3[01])— Day: 01–09, 10–29, or 30–31.
Important caveat: Regex cannot validate calendar logic (February 30 would pass this pattern). Always follow up with a new Date() parse and a sanity check.
MM/DD/YYYY:
/^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/(\d{4})$/Pattern 5: Hex Color Codes
/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/What it matches: #ff6600, #F60, #AABBCC
Breakdown:
#— Literal hash.([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})— Either 6 or 3 hex digits (the 3-digit shorthand doubles each digit:#F60=#FF6600).
const hexColor = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
console.log(hexColor.test("#ff6600")); // true
console.log(hexColor.test("#GGG")); // falsePattern 6: Strong Password with Lookaheads
This is where lookaheads shine. You need to enforce multiple independent rules (uppercase, digit, special char) without prescribing their order.
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()\-_=+]).{8,}$/What it requires:
- At least one uppercase letter
- At least one lowercase letter
- At least one digit
- At least one special character from the listed set
- Minimum 8 characters total
Breakdown of lookaheads:
(?=.*[A-Z])— Positive lookahead: somewhere ahead in the string, find an uppercase letter. The.*allows any characters before it.(?=.*[a-z])— Same for lowercase.(?=.*\d)— Same for digit.(?=.*[!@#$%^&*()\-_=+])— Same for special character..{8,}$— After all lookaheads pass, require at least 8 of any character.
Lookaheads are zero-width assertions — they check without consuming characters. This lets you enforce conditions that apply to the string as a whole, regardless of where within it the matching characters appear.
const strongPassword = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*()\-_=+]).{8,}$/;
console.log(strongPassword.test("Passw0rd!")); // true
console.log(strongPassword.test("password")); // false (no uppercase, no digit, no special)Pattern 7: Capturing Groups and Backreferences
Capturing groups let you extract sub-matches, and backreferences let you reference them later in the same pattern.
Extract date parts:
const dateRegex = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
const match = "2026-06-04".match(dateRegex);
// match[1] = "2026" (year)
// match[2] = "06" (month)
// match[3] = "04" (day)Backreference — find repeated words:
/\b(\w+)\s+\1\b/giWhat it matches: Accidentally repeated words like "the the" or "in in".
(\w+)— Captures any word.\s+— One or more whitespace characters.\1— Backreference: must match exactly what group 1 captured./gi— Global (find all), case-insensitive.
const repeatedWord = /\b(\w+)\s+\1\b/gi;
"the the cat sat on on the mat".replace(repeatedWord, "$1");
// "the cat sat on the mat"Pattern 8: Non-Greedy Matching
By default, quantifiers are greedy — they match as many characters as possible. This causes trouble when you want to match the shortest possible string.
Greedy (wrong for this use case):
"<b>bold</b> and <i>italic</i>".match(/<.+>/g);
// ["<b>bold</b> and <i>italic</i>"] — matched everything between first < and last >Non-greedy (correct):
"<b>bold</b> and <i>italic</i>".match(/<.+?>/g);
// ["<b>", "</b>", "<i>", "</i>"] — each tag matched individuallyAdding ? after a quantifier makes it non-greedy (also called lazy): *?, +?, {n,m}?. The engine now matches the minimum it can while still satisfying the overall pattern.
Warning: Non-greedy patterns are not a silver bullet for parsing HTML. Nested structures and attributes can still fool them. Use a proper HTML parser for DOM manipulation.
Pattern 9: URL Slug Generation
A slug is a URL-safe string derived from a title or heading: lowercase, hyphens instead of spaces, no special characters.
Two-step slugify:
function slugify(str) {
return str
.toLowerCase()
.replace(/[^a-z0-9\s\-]/g, "") // remove non-alphanumeric (except hyphens and spaces)
.replace(/[\s\-]+/g, "-") // collapse spaces and hyphens into one hyphen
.replace(/^\-|\-$/g, ""); // trim leading/trailing hyphens
}
console.log(slugify("Hello, World! How's it going?"));
// "hello-world-hows-it-going"Pattern breakdown:
/[^a-z0-9\s\-]/g— Remove any character that is NOT a lowercase letter, digit, whitespace, or hyphen./[\s\-]+/g— Replace one or more whitespace/hyphen sequences with a single hyphen./^\-|\-$/g— Trim hyphens from the start or end of the result.
Pattern 10: Whitespace Cleanup
Trim leading and trailing whitespace (equivalent to .trim()):
/^\s+|\s+$/gCollapse multiple spaces into one:
/\s{2,}/gRemove all whitespace:
/\s/gClean a multi-line text block:
const raw = " Hello World \n How are you? ";
const clean = raw.trim().replace(/\s{2,}/g, " ");
// "Hello World \n How are you?"Normalize line endings (convert Windows CRLF to Unix LF):
text.replace(/\r\n/g, "\n");Pattern 11: IPv4 Address
/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/What it matches: Valid IPv4 addresses from 0.0.0.0 to 255.255.255.255.
Breakdown of each octet:
25[0-5]— 250 to 255.2[0-4]\d— 200 to 249.1\d\d— 100 to 199.[1-9]?\d— 0 to 99 (with optional leading non-zero digit).
The pattern repeats three more times ({3}) with a dot separator.
Pattern 12: Credit Card Number (Luhn-Ignorant Format Check)
/^(4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12})$/This pattern checks the format of major card types:
- Visa: starts with 4, 13 or 16 digits.
- Mastercard: starts with 51–55, 16 digits.
- Amex: starts with 34 or 37, 15 digits.
- Diners Club: starts with 300–305, 36 or 38, 14 digits.
- Discover: starts with 6011 or 65, 16 digits.
Always pair with Luhn algorithm validation for real payment flows. Regex alone cannot detect a transcription error in the middle of a card number.
Pattern 13: Extract All Links from HTML
/href=["']([^"']+)["']/giWhat it matches: The URL inside href attributes.
const html = '<a href="https://example.com">link</a> <a href="/about">about</a>';
const links = [...html.matchAll(/href=["']([^"']+)["']/gi)].map(m => m[1]);
// ["https://example.com", "/about"]Breakdown:
href=— Literal.["']— Either a double or single quote.([^"']+)— Capture one or more characters that are not quotes.["']— Closing quote.
Pattern 14: Find TODOs and FIXMEs in Code
/\/\/\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s].*/giWhat it matches: Single-line code comments containing action markers.
# Python version (hash comments)
/#\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s].*/giThis is the kind of pattern you'd use in a file-scanning script to produce a technical debt report. Pair it with filename and line number capture to build a full action list.
Catastrophic Backtracking: The Silent Performance Killer
This is the most important warning in this entire guide.
Backtracking is how regex engines explore alternative match paths when a pattern fails part-way through. In most patterns this is fast and imperceptible. But certain pattern structures cause exponential backtracking — the engine explores an astronomically large number of paths and effectively hangs.
Classic dangerous pattern:
/^(a+)+$/Testing this against "aaaaaaaaaaaaaaaaab" (many as followed by one b) can take seconds, minutes, or longer — because there are exponentially many ways to partition the as across the two + quantifiers before the engine concludes b doesn't match $.
Why it happens: The outer + and the inner + are both greedy and nested. When the overall match fails, the engine systematically tries every combination of how to divide the captured characters between the two quantifiers. For n characters that's 2^n possibilities.
Other dangerous patterns:
/(a|aa)+b/ # alternation of overlapping patterns under repetition
/(\w+\s?)+$/ # repeated groups with optional elementsHow to avoid it:
- Avoid nested quantifiers on overlapping patterns.
(a+)+is almost always a bug. - Use possessive quantifiers where supported (
a++in Java, PCRE). They don't backtrack. - Use atomic groups where supported (
(?>a+)) to prevent the engine from re-entering a group after a partial match. - Test with adversarial inputs in your Regex Tester: a string that almost-but-not-quite matches your pattern, especially with lots of repeated characters.
- Set a timeout at the application level when executing user-supplied patterns.
- Consider alternative approaches: sometimes a multi-pass string operation is safer and more readable than a single complex regex.
Practical Testing Workflow
Even experienced developers test regex iteratively. Here's an effective workflow:
- Start with a simple version that matches your happy-path examples.
- Add edge cases one at a time: empty strings, maximum-length inputs, Unicode characters, leading/trailing whitespace, special characters.
- Test rejection cases — strings that should not match. A regex that's too permissive is often worse than no validation at all.
- Try adversarial inputs for backtracking risk: long strings of repeated characters that almost match.
- Document your pattern with a comment explaining what it validates and any known limitations. Future-you will be grateful.
The Regex Tester lets you paste a pattern, write test strings, and instantly see which match and which don't — without switching between a browser console and your editor.
Quick Reference: Complete Pattern Cheatsheet
| Pattern | Purpose |
|---|---|
/^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/ |
Email validation |
/^(https?:\/\/)([\w\-]+\.)+[\w\-]+(\/[\w\-./?%&=+#]*)?$/i |
URL validation |
/^[+]?[(]?[0-9]{3}[)]?[\s.\-]?[0-9]{3}[\s.\-]?[0-9]{4}$/ |
US phone number |
| `/^(\d{4})-(0[1-9] | 1[0-2])-(0[1-9] |
| `/^#([A-Fa-f0-9]{6} | [A-Fa-f0-9]{3})$/` |
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/ |
Strong password |
/\b(\w+)\s+\1\b/gi |
Repeated words |
/<.+?>/g |
HTML tags (non-greedy) |
/[^a-z0-9\s\-]/g |
Slugify (remove invalid chars) |
| `/^\s+ | \s+$/g` |
/\s{2,}/g |
Collapse spaces |
| `/^(25[0-5] | 2[0-4]\d |
/href=["']([^"']+)["']/gi |
Extract hrefs |
| `///\s*(TODO | FIXME)[:\s].*/gi` |
Closing Thoughts
Regex is a precision tool. Used well, it cuts through string-handling problems that would otherwise require dozens of lines of code. Used carelessly, it produces patterns that are hard to read, brittle on edge cases, and potentially dangerous from a performance perspective.
The habits that separate good regex usage from problematic usage are simple: test thoroughly, document your patterns, be especially careful with nested quantifiers, and don't try to solve deeply structured problems (like full HTML parsing or phone number internationalization) with regex alone when a dedicated parser or library is the better choice.
For everything else — validation, extraction, transformation, cleanup — regex is extraordinarily useful. The patterns in this guide are a starting point. Fork them, extend them, test edge cases in the Regex Tester, and build the muscle memory to read and write them fluently.
You might also like
How to Compare Two Text Files Online (and Read the Differences)
How to Compare Two Text Files Online and Read the Differences At some point, almost every developer,…
Read moreJSON vs CSV vs YAML: Which Format Should You Use?
JSON vs CSV vs YAML: Which Format Should You Use? Every developer reaches a point in a project where…
Read moreWhat Is JSON? Complete Guide to JSON Formatting
What Is JSON? Complete Guide to JSON Formatting JSON — JavaScript Object Notation — is the lingua fr…
Read more