Developer Tools·13 min read·By sourcecodestack Editorial Team

Regex Patterns Every Developer Should Know

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"));           // false

Pattern 2: URL

/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.


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"));    // false

Pattern 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/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"

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 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.


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+$/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");

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.


/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.

Pattern 14: Find TODOs and FIXMEs in Code

/\/\/\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.


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 elements

How to avoid it:

  1. Avoid nested quantifiers on overlapping patterns. (a+)+ is almost always a bug.
  2. Use possessive quantifiers where supported (a++ in Java, PCRE). They don't backtrack.
  3. Use atomic groups where supported ((?>a+)) to prevent the engine from re-entering a group after a partial match.
  4. Test with adversarial inputs in your Regex Tester: a string that almost-but-not-quite matches your pattern, especially with lots of repeated characters.
  5. Set a timeout at the application level when executing user-supplied patterns.
  6. 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:

  1. Start with a simple version that matches your happy-path examples.
  2. Add edge cases one at a time: empty strings, maximum-length inputs, Unicode characters, leading/trailing whitespace, special characters.
  3. Test rejection cases — strings that should not match. A regex that's too permissive is often worse than no validation at all.
  4. Try adversarial inputs for backtracking risk: long strings of repeated characters that almost match.
  5. 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