Regular Expressions and Pattern Matching

1. RegExp Literal and Constructor Syntax

Syntax Form Description Example
Literal Syntax /pattern/flags Most common; compiled at parse time; cannot use variables in pattern /hello/i
Constructor new RegExp(pattern, flags) Dynamic patterns; compiled at runtime; pattern is string (escape backslashes!) new RegExp('\\d+', 'g')
Constructor (no new) RegExp(pattern, flags) Same as with new; returns RegExp instance RegExp('test')
From RegExp new RegExp(regexp, flags) Clone with optional new flags; ES2015+ new RegExp(/test/i, 'g')
Property Type Description Example
source String Pattern text (without delimiters and flags) /abc/i.source // "abc"
flags String All flags as string; alphabetically sorted /a/gi.flags // "gi"
global Boolean Has g flag /a/g.global // true
ignoreCase Boolean Has i flag /a/i.ignoreCase // true
multiline Boolean Has m flag /a/m.multiline // true
dotAll Boolean Has s flag (. matches newlines) /a/s.dotAll // true
unicode Boolean Has u flag /a/u.unicode // true
sticky Boolean Has y flag /a/y.sticky // true
lastIndex Number Start position for next match; used with g or y flags regex.lastIndex = 0

Example: RegExp creation and properties

// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
#x26;"
);
} const userInput = 'hello.world'; const escaped = escapeRegex(userInput); // "hello\\.world" const safe = new RegExp(escaped); // matches literal "hello.world"
Note: Use literals /pattern/flags for static patterns (better performance); constructor for dynamic patterns. Remember to double-escape backslashes in strings: '\\d' not '\d'.

2. RegExp Flags and Global Modifiers

Flag Name Description Effect
g Global Find all matches, not just first; affects exec(), test(), match(), replace() Multiple matches
i Ignore Case Case-insensitive matching; [a-z] matches both cases /a/i matches 'A' and 'a'
m Multiline ^ and $ match line boundaries, not just string start/end ^word matches after \n
s ES2018 DotAll . (dot) matches newlines; normally dot excludes \n \r \u2028 \u2029 /.+/s matches across lines
u ES2015 Unicode Full Unicode support; astral characters, Unicode escapes, properties \u{1F600} and \p{...}
y ES2015 Sticky Match must start at lastIndex; no skipping ahead For tokenizers/parsers
d ES2022 Indices Capture group indices in .indices property of match result match.indices[0]

Example: Flag behaviors

const str = 'Hello World\nHello Again';

// g - global (all matches)
str.match(/hello/i); // ["Hello"] (first only)
str.match(/hello/gi); // ["Hello", "Hello"] (all)

/\d+/g.test('123'); // true
/\d+/g.test('123'); // false (lastIndex moved!)

// i - ignore case
/hello/.test('HELLO'); // false
/hello/i.test('HELLO'); // true

// m - multiline (^ $ match line boundaries)
const multi = 'line1\nline2\nline3';
multi.match(/^line/g); // ["line"] (only first)
multi.match(/^line/gm); // ["line", "line", "line"] (each line)

/world$/.test('hello\nworld'); // true (end of string)
/world$/m.test('world\nhello'); // true (end of line)

// s - dotAll (. matches newlines)
/.+/.test('hello\nworld'); // false (. stops at \n)
/.+/s.test('hello\nworld'); // true (. matches \n)

'a\nb'.match(/a.b/); // null
'a\nb'.match(/a.b/s); // ["a\nb"]

// u - unicode (proper astral character handling)
'😀'.match(/./); // ["�"] ❌ (high surrogate only)
'😀'.match(/./u); // ["😀"] ✓

// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test('😀'); // true
/\p{Script=Greek}/u.test('α'); // true
/\p{Letter}/u.test('A'); // true

// Count code points, not code units
'😀'.length; // 2
'😀'.match(/./gu); // ["😀"] (1 match)
'😀'.match(/./g); // ["�","�"] (2 matches)

// y - sticky (match at exact position)
const sticky = /\d+/y;
sticky.lastIndex = 0;
sticky.exec('123 456'); // ["123"]
sticky.lastIndex; // 3

sticky.exec('123 456'); // null (position 3 is space)
sticky.lastIndex; // 0 (reset on failure)

// Without y, would skip ahead and match 456
const notSticky = /\d+/g;
notSticky.lastIndex = 3;
notSticky.exec('123 456'); // ["456"]

// d - indices (capture group positions)
const indicesRe = /(\d+)-(\d+)/d;
const match = indicesRe.exec('id: 123-456');

match[1]; // "123"
match[2]; // "456"
match.indices[1]; // [4, 7] (position of "123")
match.indices[2]; // [8, 11] (position of "456")

// Combine flags
const combined = /pattern/gimsu;
combined.flags; // "gimsu" (alphabetically sorted)
Warning: Global g flag makes RegExp stateful (lastIndex). Reset with regex.lastIndex = 0 or use string methods. Always use u flag for Unicode text!

3. Pattern Matching Methods (test, exec, match)

Method Syntax Description Returns
test() regex.test(str) Check if pattern matches; fastest for existence check Boolean
exec() regex.exec(str) Get match details with capture groups; use in loop with /g flag Array | null
match() str.match(regex) Without /g: like exec(); with /g: array of all matches (no details) Array | null
matchAll() ES2020 str.matchAll(regex) Iterator of all matches with full details; regex must have /g Iterator

Example: Pattern matching methods

const str = 'The year 2025 and 2026';

// test() - simple existence check
/\d+/.test(str); // true
/cat/.test(str); // false

// Use for validation
function isEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// exec() - detailed match info
const yearRe = /(\d{4})/;
const match = yearRe.exec(str);
match[0]; // "2025" (full match)
match[1]; // "2025" (capture group 1)
match.index; // 9 (position)
match.input; // original string

// exec() with /g flag (iterate)
const yearReGlobal = /(\d{4})/g;
let m;
while ((m = yearReGlobal.exec(str)) !== null) {
    console.log(m[1], 'at', m.index);
    // "2025" at 9
    // "2026" at 18
}

// match() without /g (same as exec)
str.match(/(\d{4})/);
// ["2025", "2025", index: 9, input: "...", groups: undefined]

// match() with /g (all matches, no details)
str.match(/\d{4}/g); // ["2025", "2026"]
str.match(/\d+/g); // ["2025", "2026"]

// matchAll() - best of both (ES2020+)
const years = str.matchAll(/(\d{4})/g);
for (const match of years) {
    console.log(match[1], 'at', match.index);
    // "2025" at 9
    // "2026" at 18
}

// Convert to array
const allMatches = [...str.matchAll(/(\d{4})/g)];

// Named capture groups
const phoneRe = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/;
const phone = '555-123-4567';
const phoneMatch = phone.match(phoneRe);

phoneMatch[1]; // "555"
phoneMatch.groups.area; // "555"
phoneMatch.groups.exchange; // "123"
phoneMatch.groups.number; // "4567"

// Multiple patterns
const email = 'user@example.com';
const emailRe = /^(?<user>[^@]+)@(?<domain>[^@]+)$/;
const emailMatch = email.match(emailRe);
emailMatch.groups.user; // "user"
emailMatch.groups.domain; // "example.com"

// No match returns null
'abc'.match(/\d+/); // null
/\d+/.test('abc'); // false
/\d+/.exec('abc'); // null

// Global test() gotcha (stateful!)
const re = /test/g;
re.test('test test'); // true (first call)
re.test('test test'); // true (second call)
re.test('test test'); // false! (lastIndex past end)
re.lastIndex; // 0 (reset)

// Solution: don't reuse global regex for test()
function hasDigit(str) {
    return /\d/.test(str); // no /g, always works
}

// Or reset lastIndex
function hasPattern(str, regex) {
    regex.lastIndex = 0;
    return regex.test(str);
}
Note: Use test() for boolean checks; match() for simple extraction; matchAll() for all matches with details. Avoid test() with global regex in loops (stateful).
Method Syntax Description Returns
replace() str.replace(regex, newStr|fn) Replace first match (or all with /g); newStr can use $1, $2, etc. String
replaceAll() ES2021 str.replaceAll(str|regex, newStr|fn) Replace all occurrences; regex must have /g flag String
split() str.split(regex, limit) Split string by pattern; optional limit; capture groups included in result Array
search() str.search(regex) Find index of first match; ignores /g flag; like indexOf for regex Index | -1
Replacement Patterns (in replace newStr argument)
Pattern Inserts Example Result
$$ Literal $ '$10'.replace(/\d+/, '$$&') "$&10"
$& Entire match 'cat'.replace(/\w+/, '[$&]') "[cat]"
$` Text before match 'a-b'.replace(/-/, '$`') "aab"
$' Text after match 'a-b'.replace(/-/, "$'") "abb"
$n Capture group n (1-99) 'John Doe'.replace(/(\w+) (\w+)/, '$2, $1') "Doe, John"
$<name> Named capture group '2025-12-17'.replace(/(?<y>\d+)-(?<m>\d+)-(?<d>\d+)/, '$<m>/$<d>/$<y>') "12/17/2025"

Example: String RegExp methods

const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x26;]'
); // "[test]"
'a-b'.replace(/-/, '(

Regular Expressions and Pattern Matching

1. RegExp Literal and Constructor Syntax

Syntax Form Description Example
Literal Syntax /pattern/flags Most common; compiled at parse time; cannot use variables in pattern /hello/i
Constructor new RegExp(pattern, flags) Dynamic patterns; compiled at runtime; pattern is string (escape backslashes!) new RegExp('\\d+', 'g')
Constructor (no new) RegExp(pattern, flags) Same as with new; returns RegExp instance RegExp('test')
From RegExp new RegExp(regexp, flags) Clone with optional new flags; ES2015+ new RegExp(/test/i, 'g')
Property Type Description Example
source String Pattern text (without delimiters and flags) /abc/i.source // "abc"
flags String All flags as string; alphabetically sorted /a/gi.flags // "gi"
global Boolean Has g flag /a/g.global // true
ignoreCase Boolean Has i flag /a/i.ignoreCase // true
multiline Boolean Has m flag /a/m.multiline // true
dotAll Boolean Has s flag (. matches newlines) /a/s.dotAll // true
unicode Boolean Has u flag /a/u.unicode // true
sticky Boolean Has y flag /a/y.sticky // true
lastIndex Number Start position for next match; used with g or y flags regex.lastIndex = 0

Example: RegExp creation and properties

// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
#x26;"
);
} const userInput = 'hello.world'; const escaped = escapeRegex(userInput); // "hello\\.world" const safe = new RegExp(escaped); // matches literal "hello.world"
Note: Use literals /pattern/flags for static patterns (better performance); constructor for dynamic patterns. Remember to double-escape backslashes in strings: '\\d' not '\d'.

2. RegExp Flags and Global Modifiers

Flag Name Description Effect
g Global Find all matches, not just first; affects exec(), test(), match(), replace() Multiple matches
i Ignore Case Case-insensitive matching; [a-z] matches both cases /a/i matches 'A' and 'a'
m Multiline ^ and $ match line boundaries, not just string start/end ^word matches after \n
s ES2018 DotAll . (dot) matches newlines; normally dot excludes \n \r \u2028 \u2029 /.+/s matches across lines
u ES2015 Unicode Full Unicode support; astral characters, Unicode escapes, properties \u{1F600} and \p{...}
y ES2015 Sticky Match must start at lastIndex; no skipping ahead For tokenizers/parsers
d ES2022 Indices Capture group indices in .indices property of match result match.indices[0]

Example: Flag behaviors

const str = 'Hello World\nHello Again';

// g - global (all matches)
str.match(/hello/i); // ["Hello"] (first only)
str.match(/hello/gi); // ["Hello", "Hello"] (all)

/\d+/g.test('123'); // true
/\d+/g.test('123'); // false (lastIndex moved!)

// i - ignore case
/hello/.test('HELLO'); // false
/hello/i.test('HELLO'); // true

// m - multiline (^ $ match line boundaries)
const multi = 'line1\nline2\nline3';
multi.match(/^line/g); // ["line"] (only first)
multi.match(/^line/gm); // ["line", "line", "line"] (each line)

/world$/.test('hello\nworld'); // true (end of string)
/world$/m.test('world\nhello'); // true (end of line)

// s - dotAll (. matches newlines)
/.+/.test('hello\nworld'); // false (. stops at \n)
/.+/s.test('hello\nworld'); // true (. matches \n)

'a\nb'.match(/a.b/); // null
'a\nb'.match(/a.b/s); // ["a\nb"]

// u - unicode (proper astral character handling)
'😀'.match(/./); // ["�"] ❌ (high surrogate only)
'😀'.match(/./u); // ["😀"] ✓

// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test('😀'); // true
/\p{Script=Greek}/u.test('α'); // true
/\p{Letter}/u.test('A'); // true

// Count code points, not code units
'😀'.length; // 2
'😀'.match(/./gu); // ["😀"] (1 match)
'😀'.match(/./g); // ["�","�"] (2 matches)

// y - sticky (match at exact position)
const sticky = /\d+/y;
sticky.lastIndex = 0;
sticky.exec('123 456'); // ["123"]
sticky.lastIndex; // 3

sticky.exec('123 456'); // null (position 3 is space)
sticky.lastIndex; // 0 (reset on failure)

// Without y, would skip ahead and match 456
const notSticky = /\d+/g;
notSticky.lastIndex = 3;
notSticky.exec('123 456'); // ["456"]

// d - indices (capture group positions)
const indicesRe = /(\d+)-(\d+)/d;
const match = indicesRe.exec('id: 123-456');

match[1]; // "123"
match[2]; // "456"
match.indices[1]; // [4, 7] (position of "123")
match.indices[2]; // [8, 11] (position of "456")

// Combine flags
const combined = /pattern/gimsu;
combined.flags; // "gimsu" (alphabetically sorted)
Warning: Global g flag makes RegExp stateful (lastIndex). Reset with regex.lastIndex = 0 or use string methods. Always use u flag for Unicode text!

3. Pattern Matching Methods (test, exec, match)

Method Syntax Description Returns
test() regex.test(str) Check if pattern matches; fastest for existence check Boolean
exec() regex.exec(str) Get match details with capture groups; use in loop with /g flag Array | null
match() str.match(regex) Without /g: like exec(); with /g: array of all matches (no details) Array | null
matchAll() ES2020 str.matchAll(regex) Iterator of all matches with full details; regex must have /g Iterator

Example: Pattern matching methods

const str = 'The year 2025 and 2026';

// test() - simple existence check
/\d+/.test(str); // true
/cat/.test(str); // false

// Use for validation
function isEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// exec() - detailed match info
const yearRe = /(\d{4})/;
const match = yearRe.exec(str);
match[0]; // "2025" (full match)
match[1]; // "2025" (capture group 1)
match.index; // 9 (position)
match.input; // original string

// exec() with /g flag (iterate)
const yearReGlobal = /(\d{4})/g;
let m;
while ((m = yearReGlobal.exec(str)) !== null) {
    console.log(m[1], 'at', m.index);
    // "2025" at 9
    // "2026" at 18
}

// match() without /g (same as exec)
str.match(/(\d{4})/);
// ["2025", "2025", index: 9, input: "...", groups: undefined]

// match() with /g (all matches, no details)
str.match(/\d{4}/g); // ["2025", "2026"]
str.match(/\d+/g); // ["2025", "2026"]

// matchAll() - best of both (ES2020+)
const years = str.matchAll(/(\d{4})/g);
for (const match of years) {
    console.log(match[1], 'at', match.index);
    // "2025" at 9
    // "2026" at 18
}

// Convert to array
const allMatches = [...str.matchAll(/(\d{4})/g)];

// Named capture groups
const phoneRe = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/;
const phone = '555-123-4567';
const phoneMatch = phone.match(phoneRe);

phoneMatch[1]; // "555"
phoneMatch.groups.area; // "555"
phoneMatch.groups.exchange; // "123"
phoneMatch.groups.number; // "4567"

// Multiple patterns
const email = 'user@example.com';
const emailRe = /^(?<user>[^@]+)@(?<domain>[^@]+)$/;
const emailMatch = email.match(emailRe);
emailMatch.groups.user; // "user"
emailMatch.groups.domain; // "example.com"

// No match returns null
'abc'.match(/\d+/); // null
/\d+/.test('abc'); // false
/\d+/.exec('abc'); // null

// Global test() gotcha (stateful!)
const re = /test/g;
re.test('test test'); // true (first call)
re.test('test test'); // true (second call)
re.test('test test'); // false! (lastIndex past end)
re.lastIndex; // 0 (reset)

// Solution: don't reuse global regex for test()
function hasDigit(str) {
    return /\d/.test(str); // no /g, always works
}

// Or reset lastIndex
function hasPattern(str, regex) {
    regex.lastIndex = 0;
    return regex.test(str);
}
Note: Use test() for boolean checks; match() for simple extraction; matchAll() for all matches with details. Avoid test() with global regex in loops (stateful).
Method Syntax Description Returns
replace() str.replace(regex, newStr|fn) Replace first match (or all with /g); newStr can use $1, $2, etc. String
replaceAll() ES2021 str.replaceAll(str|regex, newStr|fn) Replace all occurrences; regex must have /g flag String
split() str.split(regex, limit) Split string by pattern; optional limit; capture groups included in result Array
search() str.search(regex) Find index of first match; ignores /g flag; like indexOf for regex Index | -1
Replacement Patterns (in replace newStr argument)
Pattern Inserts Example Result
$$ Literal $ '$10'.replace(/\d+/, '$$&') "$&10"
$& Entire match 'cat'.replace(/\w+/, '[$&]') "[cat]"
$` Text before match 'a-b'.replace(/-/, '$`') "aab"
$' Text after match 'a-b'.replace(/-/, "$'") "abb"
$n Capture group n (1-99) 'John Doe'.replace(/(\w+) (\w+)/, '$2, $1') "Doe, John"
$<name> Named capture group '2025-12-17'.replace(/(?<y>\d+)-(?<m>\d+)-(?<d>\d+)/, '$<m>/$<d>/$<y>') "12/17/2025"

Example: String RegExp methods

|$\')'); // "a(a|b)b" // Capture groups 'John Smith'.replace(/(\w+) (\w+)/, '$2, $1'); // "Smith, John" // Named groups const date = '2025-12-17'; date.replace( /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/, '
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;month>/
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;day>/
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;year>'
); // "12/17/2025" // Replace with function str.replace(/\b\w+\b/g, match => match.toUpperCase()); // "HELLO WORLD, HELLO AGAIN" // Function receives: match, p1, p2, ..., offset, string, groups '1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => { return parseInt(p1) * 2; }); // "2 plus 4 equals 6" // Advanced replacements const text = 'Price: $10.50, Total: $20.00'; text.replace(/\$(\d+\.\d+)/g, (match, price) => { return `${(parseFloat(price) * 1.1).toFixed(2)}`; }); // "Price: $11.55, Total: $22.00" // split - by regex 'a1b2c3'.split(/\d/); // ["a", "b", "c", ""] 'one,two;three:four'.split(/[,;:]/); // ["one", "two", "three", "four"] // split - with limit 'a-b-c-d'.split(/-/, 2); // ["a", "b"] // split - capture groups included in result 'a1b2c'.split(/(\d)/); // ["a", "1", "b", "2", "c"] // split - whitespace ' hello world '.split(/\s+/); // ["", "hello", "world", ""] ' hello world '.trim().split(/\s+/); // ["hello", "world"] // search - find index str.search(/world/i); // 6 str.search(/cat/); // -1 // search vs indexOf 'hello'.indexOf('l'); // 2 (first 'l') 'hello'.search(/l/); // 2 (first 'l') // search with patterns 'abc123xyz'.search(/\d/); // 3 (first digit) 'test@email.com'.search(/@/); // 4 // Use cases // 1. Format phone numbers function formatPhone(phone) { return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'); } formatPhone('5551234567'); // "(555) 123-4567" // 2. Sanitize HTML function escapeHtml(str) { return str .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } // 3. Slugify function slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } slugify('Hello World!'); // "hello-world" // 4. Extract data const email = 'Contact: user@example.com'; const match = email.match(/[\w.]+@[\w.]+/); const emailAddr = match ? match[0] : null;
Note: Use replace() with functions for complex transformations. replaceAll() requires /g flag for regex. split() with capture groups includes delimiters in result.

5. Character Classes and Quantifiers

Pattern Matches Description Example
. Any character Except newline (unless /s flag); use [\s\S] or /s for any /a.c/.test('abc') // true
\d Digit [0-9] ASCII digits only; use \p{N} with /u for Unicode digits /\d+/.test('123') // true
\D Non-digit [^0-9] Anything except ASCII digits /\D/.test('a') // true
\w Word [A-Za-z0-9_] Alphanumeric + underscore; ASCII only /\w+/.test('hello') // true
\W Non-word [^\w] Anything except word characters /\W/.test('@') // true
\s Whitespace Space, tab, newline, etc.: [ \t\n\r\f\v] /\s+/.test('a b') // true
\S Non-whitespace Anything except whitespace /\S/.test('a') // true
[abc] Character set Any single character in set; can use ranges /[aeiou]/.test('e') // true
[^abc] Negated set Any character NOT in set /[^0-9]/.test('a') // true
[a-z] Range Character range; case-sensitive /[a-z]/.test('m') // true
\b Word boundary Position between \w and \W; zero-width /\bword\b/.test('a word') // true
\B Non-word boundary Opposite of \b /\Bword/.test('sword') // true
^ Start of string/line String start, or line start with /m flag /^hello/.test('hello') // true
$ End of string/line String end, or line end with /m flag /world$/.test('world') // true
Quantifier Matches Description Example
* 0 or more Greedy; matches as many as possible /a*/.test('') // true
+ 1 or more Greedy; at least one required /a+/.test('aaa') // true
? 0 or 1 Optional; makes preceding element optional /colou?r/.test('color') // true
{n} Exactly n Precise count /\d{3}/.test('123') // true
{n,} n or more At least n /\d{2,}/.test('12') // true
{n,m} Between n and m Inclusive range /\d{2,4}/.test('123') // true
*? 0+ (lazy) Non-greedy; matches as few as possible /<.*?>/.exec('<a><b>')[0] // "<a>"
+? 1+ (lazy) Non-greedy; at least one /a+?/.exec('aaa')[0] // "a"
?? 0-1 (lazy) Non-greedy optional /a??/.exec('aa')[0] // ""
{n,m}? n-m (lazy) Non-greedy range /\d{2,4}?/.exec('12345')[0] // "12"

Example: Character classes and quantifiers

// Basic character classes
/\d+/.test('123'); // true (one or more digits)
/\D+/.test('abc'); // true (one or more non-digits)
/\w+/.test('hello_123'); // true (word characters)
/\s+/.test('   '); // true (whitespace)

// Dot (any character except newline)
/.+/.test('hello'); // true
/.+/.test('hello\nworld'); // false (. doesn't match \n)
/.+/s.test('hello\nworld'); // true (dotAll flag)

// Character sets
/[aeiou]/.test('hello'); // true (has vowel)
/[0-9]/.test('5'); // true (digit)
/[a-z]/i.test('A'); // true (case insensitive)
/[a-zA-Z0-9]/.test('x'); // true (alphanumeric)

// Negated sets
/[^0-9]/.test('a'); // true (not a digit)
/[^aeiou]/.test('b'); // true (not a vowel)

// Ranges
/[a-z]/.test('m'); // true
/[A-Z]/.test('M'); // true
/[0-9]/.test('5'); // true
/[a-zA-Z]/.test('X'); // true

// Word boundaries
/\bhello\b/.test('hello world'); // true
/\bhello\b/.test('helloworld'); // false
/\bworld\b/.test('hello world'); // true

// Extract whole words
'hello world'.match(/\b\w+\b/g); // ["hello", "world"]

// Start/End anchors
/^hello/.test('hello world'); // true
/^hello/.test('say hello'); // false
/world$/.test('hello world'); // true
/world$/.test('world peace'); // false

// Full string match
/^hello$/.test('hello'); // true
/^hello$/.test('hello world'); // false

// Greedy quantifiers (default)
'<a><b><c>'.match(/<.+>/)[0]; // "<a><b><c>" (greedy)
'aaaa'.match(/a+/)[0]; // "aaaa" (all)

// Non-greedy (lazy) quantifiers
'<a><b><c>'.match(/<.+?>/g); // ["<a>", "<b>", "<c>"]
'aaaa'.match(/a+?/)[0]; // "a" (minimal)

// Practical examples
// 1. Email (simple)
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test('user@example.com'); // true

// 2. Phone number
/^\d{3}-\d{3}-\d{4}$/.test('555-123-4567'); // true

// 3. Hex color
/^#[0-9A-F]{6}$/i.test('#FF5733'); // true

// 4. URL (simple)
/^https?:\/\/[\w.-]+/.test('https://example.com'); // true

// 5. Username (alphanumeric, 3-16 chars)
/^\w{3,16}$/.test('user_123'); // true

// 6. Password (at least 8 chars, has digit)
/^(?=.*\d).{8,}$/.test('Pass123!'); // true

// 7. Extract hashtags
'#javascript #coding #webdev'.match(/#\w+/g);
// ["#javascript", "#coding", "#webdev"]

// 8. Match HTML tags (non-greedy)
'<div>Hello</div>'.match(/<[^>]+>/g);
// ["<div>", "</div>"]

// 9. Trim whitespace
'  hello  '.replace(/^\s+|\s+$/g, ''); // "hello"

// 10. Multiple spaces to single
'hello    world'.replace(/\s+/g, ' '); // "hello world"

// Combining patterns
const urlRe = /^(https?:\/\/)?([\w.-]+)(:\d+)?(\/[\w.-]*)*\??/;

// Optional elements with ?
/colou?r/.test('color'); // true (US)
/colou?r/.test('colour'); // true (UK)

// Exact count
/^\d{5}$/.test('12345'); // true (5 digits)
/^\d{3}-\d{2}-\d{4}$/.test('123-45-6789'); // true (SSN format)

// Range
/^[a-z]{3,10}$/i.test('hello'); // true (3-10 letters)
Warning: Greedy quantifiers (+, *) can cause catastrophic backtracking with complex patterns. Use lazy quantifiers (+?, *?) or atomic groups when appropriate.

6. Lookahead and Lookbehind Assertions

Assertion Syntax Description Example
Positive Lookahead (?=pattern) Assert pattern matches ahead; zero-width (doesn't consume) /\d(?=px)/.exec('5px')[0] // "5"
Negative Lookahead (?!pattern) Assert pattern does NOT match ahead; zero-width /\d(?!px)/.test('5em') // true
Positive Lookbehind ES2018 (?<=pattern) Assert pattern matches behind; zero-width /(?<=\$)\d+/.exec('$50')[0] // "50"
Negative Lookbehind ES2018 (?<!pattern) Assert pattern does NOT match behind; zero-width /(?<!\$)\d+/.test('€50') // true

Example: Lookahead and lookbehind

// Positive lookahead (?=...)
// Match digit followed by "px" but don't include "px"
'5px 10em'.match(/\d+(?=px)/g); // ["5"]

// Password validation (at least one digit)
/^(?=.*\d).{8,}$/.test('Pass123!'); // true
/^(?=.*\d).{8,}$/.test('Password'); // false (no digit)

// Multiple lookaheads (AND conditions)
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$/;
strongPassword.test('Pass123!'); // true
strongPassword.test('password'); // false (no uppercase, digit, special)

// Negative lookahead (?!...)
// Match word NOT followed by "script"
'Java JavaScript'.match(/\bJava(?!Script)\b/); // ["Java"]

// Select numbers not followed by px
'5px 10em 15%'.match(/\d+(?!px)/g); // ["1", "0", "1", "5"]
// (matches last digit of 10, and 15)

// Better: not followed by px unit
'5px 10em 15%'.match(/\d+(?!px\b)/g); // ["10", "15"]

// Positive lookbehind (?<=...)
// Match digits preceded by $
'$50 €30 £40'.match(/(?<=\$)\d+/g); // ["50"]

// Extract value after key
'price=100 quantity=5'.match(/(?<==)\d+/g);
// ["100", "5"]

// Match word after "Mr. "
'Mr. Smith, Ms. Jones'.match(/(?<=Mr\. )\w+/);
// ["Smith"]

// Negative lookbehind (?<!...)
// Match digits NOT preceded by $
'$50 €30 £40'.match(/(?<!\$)\d+/g); // ["30", "40"]

// Word not preceded by "no "
'no cats, dogs, no birds'.match(/(?<!no )\b\w+s\b/g);
// ["dogs"] (cats and birds are preceded by "no ")

// Complex combinations
// 1. Price in $ (with decimal)
const priceRe = /(?<=\$)\d+(?:\.\d{2})?(?=\s|$)/g;
'Items: $10.50 and $25.00'.match(priceRe);
// ["10.50", "25.00"]

// 2. Password: 8+ chars, has lowercase, uppercase, digit
const passRe = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
passRe.test('Secure123'); // true
passRe.test('secure123'); // false (no uppercase)
passRe.test('SECURE123'); // false (no lowercase)
passRe.test('SecurePass'); // false (no digit)

// 3. Extract hashtags not in code blocks
// (not preceded by backtick)
const text = 'Use #javascript but not `#code`';
text.match(/(?<!`[^`]*)#\w+(?![^`]*`)/g); // ["#javascript"]

// 4. Numbers with thousands separator
const numRe = /\b(?<!\d,)\d{1,3}(?:,\d{3})*(?!\d)/g;
'1,000 and 10,000 and 999'.match(numRe);
// ["1,000", "10,000", "999"]

// 5. Email username (before @)
const emailRe = /\w+(?=@)/;
'user@example.com'.match(emailRe)[0]; // "user"

// 6. URL domain (after //)
const domainRe = /(?<=:\/\/)[^\/]+/;
'https://example.com/path'.match(domainRe)[0];
// "example.com"

// 7. Word boundaries with punctuation
// Word not followed by alphanumeric
'hello, world!'.match(/\w+(?!\w)/g);
// ["hello", "world"]

// 8. CSS selector (class not in comments)
const css = '.btn { } /* .comment */';
css.match(/(?<!\/\*.*)\.[a-z-]+(?!.*\*\/)/g);
// Tricky - better parsed differently

// Zero-width assertion chaining
// Number between 1-100
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('50'); // true
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('0'); // false
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('101'); // false

// Assertions don't consume characters
const re = /(?=\d{3})\d{2}/;
re.exec('12345'); // ["12"] (matched at position 0)
// Lookahead checked 3 digits, but only consumed 2

// Use in replace
// Insert comma in numbers
'1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// "1,234,567"
Note: Assertions are zero-width (don't consume characters). Lookaheads work everywhere; lookbehinds require ES2018+. Use for complex validations and extractions.

7. Named Capture Groups and Replacement

Feature Syntax Description Example
Named Group (?<name>pattern) Capture group with identifier; access via .groups property /(?<year>\d{4})/
Group Reference \k<name> Backreference to named group within pattern /(?<tag>\w+)</\k<tag>/
Replacement $<name> Reference named group in replacement string .replace(re, '$<name>')
Non-capturing Group (?:pattern) Group without capturing; for grouping only; better performance /(?:abc)+/
Numbered Group (pattern) Traditional capture group; access via index /(\d+)/
Backreference \1 \2 ... Reference numbered group within pattern /(\w)\1/ (double char)

Example: Named groups and backreferences

// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;month>/
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;day>/
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;year>'
);
// "12/17/2025" // Function replacement with named groups date.replace(dateRe, (match, y, m, d, offset, str, groups) => { return `${groups.month}/${groups.day}/${groups.year}`; }); // Backreference to named group const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/; htmlTag.test('<div>content</div>'); // true htmlTag.test('<div>content</span>'); // false (tag mismatch) // Find duplicate words const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i; duplicateRe.test('the the'); // true duplicateRe.test('the cat'); // false // Email parsing const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/; const email = 'user@example.com'.match(emailRe); email.groups.user; // "user" email.groups.domain; // "example" email.groups.tld; // "com" // URL parsing const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/; const url = 'https://example.com/path/to/page'.match(urlRe); url.groups.protocol; // "https" url.groups.domain; // "example.com" url.groups.path; // "/path/to/page" // Phone number formatting const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/; '5551234567'.replace(phoneRe, '(
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;area>)
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;exchange>-
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;number>'
);
// "(555) 123-4567" // Non-capturing groups (?:...) // Group without capturing (performance) /(?:abc)+/.test('abcabc'); // true 'abcabc'.match(/(?:abc)+/)[0]; // "abcabc" 'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups) // Compare to capturing 'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture) // Use for alternation /(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group) // Numbered backreferences // Find doubled characters /(\w)\1/.test('hello'); // true (ll) 'hello'.match(/(\w)\1/)[0]; // "ll" // Find repeated words /(\b\w+\b)\s+\1/.test('the the'); // true // Match balanced quotes /'([^']*)'/.exec("'hello'")[1]; // "hello" // HTML attributes /(\w+)="([^"]*)"/.exec('class="btn"'); // ["class=\"btn\"", "class", "btn"] // Combining named and numbered groups const re = /(?<name>\w+):(\d+)/; const m = 'user:123'.match(re); m.groups.name; // "user" m[2]; // "123" (numbered after named) // Replace with function (named groups) 'John Smith, Jane Doe'.replace( /(?<first>\w+) (?<last>\w+)/g, (match, first, last, offset, string, groups) => { return `${groups.last}, ${groups.first}`; } ); // "Smith, John, Doe, Jane" // Case conversion in replacement 'user_name'.replace( /_(?<char>\w)/g, (match, char, offset, string, groups) => { return groups.char.toUpperCase(); } ); // "userName" // Swap parts 'firstName lastName'.replace( /(?<first>\w+) (?<last>\w+)/, '
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;last>
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;first>'
); // "lastName firstName" // Optional named groups const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/; 'example.com'.match(optionalRe).groups; // {protocol: undefined, domain: "example.com"} 'https://example.com'.match(optionalRe).groups; // {protocol: "https://", domain: "example.com"} // matchAll with named groups const text = 'user:123 admin:456'; const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g); for (const match of matches) { console.log(match.groups.role, match.groups.id); // "user" "123" // "admin" "456" }
Note: Named groups improve readability and maintainability. Use (?:...) for grouping without capturing (better performance). Backreferences \k<name> or \1 match same text as group.

Section 11 Summary

  • Creation: Literal /pattern/flags for static (compile-time); constructor new RegExp(str, flags) for dynamic (escape backslashes: '\\d')
  • Flags: g (global), i (case-insensitive), m (multiline ^$), s (dotAll), u (Unicode), y (sticky), d (indices); g makes regex stateful
  • Methods: test() boolean; exec() details + loop with g; match() simple/all; matchAll() iterator with details (requires g)
  • String methods: replace/replaceAll modify; split divide (includes captures); search find index; use $1 $<name> in replacement
  • Patterns: \d \w \s classes; . [abc] [^abc] [a-z] sets; ^ $ \b anchors; + * ? {n,m} quantifiers; add ? for lazy
  • Assertions: (?=...) (?!...) lookahead; (?<=...) (?<!...) lookbehind; zero-width (don't consume); chain for complex validation
  • Groups: (?<name>...) named capture; (?:...) non-capturing; \k<name> \1 backreferences; $<name> $1 in replacements