sourcecodestack Team
Tools, guides & how-tos
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.
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 |
Flags (also called modifiers) change how the entire pattern behaves. In JavaScript they’re appended after the closing slash: /pattern/flags.
g — GlobalWithout 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-insensitiveMakes the pattern ignore case. /hello/i matches hello, Hello, HELLO, and any mixed case.
/hello/i.test("Hello World"); // true
m — MultilineChanges ^ 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 — UnicodeEnables 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.
/^[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")); // false
/https?:\/\/[^\s/$.?#].[^\s]*/
A more robust version:
/^(https?:\/\/)([\w\-]+\.)+[\w\-]+(\/[\w\-./?%&=+#]*)?$/i
What it matches: HTTP and HTTPS URLs.
Breakdown:
^(https?:\/\/) — Protocol: http:// or https://.([\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.
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");
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})$/
/^#([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")); // false
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:
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)
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/gi
What 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"
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 individually
Adding ? 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.
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.Trim leading and trailing whitespace (equivalent to .trim()):
/^\s+|\s+$/g
Collapse multiple spaces into one:
/\s{2,}/g
Remove all whitespace:
/\s/g
Clean 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");
/^(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.
/^(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:
Always pair with Luhn algorithm validation for real payment flows. Regex alone cannot detect a transcription error in the middle of a card number.
/href=["']([^"']+)["']/gi
What 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./\/\/\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s].*/gi
What it matches: Single-line code comments containing action markers.
# Python version (hash comments)
/#\s*(TODO|FIXME|HACK|XXX|NOTE)[:\s].*/gi
This 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.
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 elements
How to avoid it:
(a+)+ is almost always a bug.a++ in Java, PCRE). They don’t backtrack.(?>a+)) to prevent the engine from re-entering a group after a partial match.Even experienced developers test regex iteratively. Here’s an effective workflow:
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.
| 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` |
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.
sourcecodestack Team
We build free, privacy-first browser tools and write practical guides on how to use them. Everything runs on your device — no uploads, no sign-ups.
At some point, almost every developer, writer, analyst, and systems administrator faces the same problem: two …
Every developer reaches a point in a project where they have to make a decision that feels small but has lasti…
A site will not load. Before you clear your cache, reboot the router, or file an angry support ticket, answer …