Skip to main content

Methods of RegExp and String in JavaScript

JavaScript provides regex functionality through two sides: methods on String objects that accept regular expressions, and methods on RegExp objects that accept strings. Knowing which method to use in each situation is the difference between writing clean, efficient pattern-matching code and struggling with unexpected results.

This guide covers every method available for working with regular expressions in JavaScript. You will learn the exact behavior of each method, how flags change their output, what they return on success and failure, and which method to reach for in every common scenario.

str.match(regexp): With and Without the g Flag

The match() method is probably the most frequently used regex method on strings. Its behavior changes dramatically depending on whether the regex has the g (global) flag.

Without the g Flag: Detailed Single Match

When the regex does not have the g flag, match() returns a result array containing the first match along with capturing group information, or null if there is no match.

const str = 'Date: 2024-03-15, Time: 14:30';
const result = str.match(/(\d{4})-(\d{2})-(\d{2})/);

console.log(result);

Output:

[
'2024-03-15', // result[0] (the full match)
'2024', // result[1] (first capturing group)
'03', // result[2] (second capturing group)
'15', // result[3] (third capturing group)
index: 6, // position of the match in the string
input: 'Date: 2024-03-15, Time: 14:30', // the original string
groups: undefined // named groups (none in this example)
]

The returned array has special properties:

PropertyDescription
[0]The full matched substring
[1], [2], ...Captured groups
indexStarting position of the match
inputThe original string
groupsObject of named capturing groups (or undefined)

Named Capturing Groups

When you use named groups, the groups property is populated:

const str = 'John is 30 years old';
const result = str.match(/(?<name>\w+) is (?<age>\d+)/);

console.log(result.groups.name); // "John"
console.log(result.groups.age); // "30"
console.log(result[1]); // "John" (also accessible by index)
console.log(result[2]); // "30"

With the g Flag: Array of All Matches

When the regex has the g flag, match() returns a plain array of all matched substrings, without any group information, index, or extra properties.

const str = 'cats and dogs and cats and birds';
const result = str.match(/cats/g);

console.log(result); // ["cats", "cats"]

A more practical example:

const html = '<div class="box"> <span id="title"> <a href="#">';
const tags = html.match(/<\w+/g);

console.log(tags); // ["<div", "<span", "<a"]
warning

With the g flag, match() returns only the matched strings. You lose all capturing group data, indexes, and the groups property. If you need that information for all matches, use matchAll() instead.

When There Is No Match

Both variants return null when no match is found:

const str = 'hello world';

console.log(str.match(/xyz/)); // null
console.log(str.match(/xyz/g)); // null

Always guard against null before working with the result:

// Common mistake: crashes if no match
const count = str.match(/xyz/g).length; // TypeError: Cannot read properties of null

// Safe approach
const matches = str.match(/xyz/g);
const count2 = matches ? matches.length : 0;

// Or with nullish coalescing
const count3 = (str.match(/xyz/g) ?? []).length;

Passing a String Instead of RegExp

If you pass a plain string to match(), JavaScript automatically converts it to a RegExp:

const str = 'Price: $10.99';

// These are equivalent:
console.log(str.match('\\d+')); // ["10"]
console.log(str.match(/\d+/)); // ["10"]
note

When passing a string, you must double-escape backslashes (\\d instead of \d). Using a regex literal is almost always clearer.

str.matchAll(regexp): Iterator of All Matches

The matchAll() method was introduced in ES2020 to solve the main limitation of match() with the g flag: losing group information. It returns an iterator that yields detailed match objects for every match.

Basic Usage

const str = 'test1 test2 test3';
const regex = /test(\d)/g;

const iterator = str.matchAll(regex);

for (const match of iterator) {
console.log(`Full: "${match[0]}", Group: "${match[1]}", Index: ${match.index}`);
}

Output:

Full: "test1", Group: "1", Index: 0
Full: "test2", Group: "2", Index: 6
Full: "test3", Group: "3", Index: 12

Each iteration yields a match object identical to what match() without the g flag would return: full match, captured groups, index, input, and groups.

The g Flag Is Required

matchAll() throws a TypeError if the regex does not have the g flag:

const str = 'hello world';

// This throws!
try {
str.matchAll(/hello/);
} catch (e) {
console.log(e.message);
// "String.prototype.matchAll called with a non-global RegExp argument"
}

// Correct usage
const matches = str.matchAll(/hello/g);

Converting to an Array

Since matchAll() returns an iterator, you can convert it to an array with the spread operator or Array.from():

const str = 'rgb(255, 128, 0) and rgb(0, 200, 100)';
const matches = [...str.matchAll(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/g)];

console.log(matches.length); // 2

console.log(matches[0][0]); // "rgb(255, 128, 0)"
console.log(matches[0][1]); // "255"
console.log(matches[0][2]); // "128"
console.log(matches[0][3]); // "0"

console.log(matches[1][0]); // "rgb(0, 200, 100)"
console.log(matches[1][1]); // "0"

With Named Groups

matchAll() fully supports named capturing groups:

const str = '2024-03-15 and 2023-12-25';
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;

for (const match of str.matchAll(regex)) {
const { year, month, day } = match.groups;
console.log(`Year: ${year}, Month: ${month}, Day: ${day}`);
}

Output:

Year: 2024, Month: 03, Day: 15
Year: 2023, Month: 12, Day: 25

matchAll() Does Not Mutate lastIndex

Unlike exec(), matchAll() does not modify the regex's lastIndex property. It creates an internal copy:

const regex = /\d+/g;
regex.lastIndex = 5;

const str = '12 34 56';
const matches = [...str.matchAll(regex)];

console.log(matches.map(m => m[0])); // ["12", "34", "56"] (starts from beginning)
console.log(regex.lastIndex); // 5 (unchanged)
tip

Prefer matchAll() over manual exec() loops whenever you need all matches with full details. It is safer (no infinite loop risk), cleaner, and does not mutate the regex state.

str.search(regexp): First Match Index

The search() method returns the index of the first match, or -1 if no match is found. It is the regex equivalent of indexOf().

Basic Usage

const str = 'Hello, World! 123';

console.log(str.search(/\d+/)); // 14 (first digit found at index 14)
console.log(str.search(/World/)); // 7
console.log(str.search(/xyz/)); // -1 (not found)

Key Characteristics

search() has several important behaviors:

const str = 'one TWO three FOUR';

// Case-insensitive search
console.log(str.search(/two/)); // -1 (case-sensitive by default)
console.log(str.search(/two/i)); // 4 (case-insensitive)

// Always returns the FIRST match index
console.log(str.search(/[A-Z]+/g)); // 4 (the g flag is ignored)

// Ignores lastIndex
const regex = /\d/g;
regex.lastIndex = 10;
const str2 = 'abc 1 def 2';
console.log(str2.search(regex)); // 4 (ignores lastIndex, starts from 0)
console.log(regex.lastIndex); // 10 (not modified)
note

search() always searches from the beginning of the string. It ignores both the g flag and lastIndex. Use it only when you need the position of the first match and nothing else.

search() vs. indexOf()

const str = 'Price: $42.99';

// indexOf works with plain strings only
console.log(str.indexOf('42')); // 8

// search works with regex patterns
console.log(str.search(/\d+\.\d+/)); // 8 (matches "42.99")

// indexOf cannot do pattern matching
console.log(str.indexOf(/\d+/)); // -1 (treated as string "/\\d+/")

Use indexOf() for simple string lookups and search() when you need pattern matching.

str.replace(regexp, replacement): Replace Matches

The replace() method searches for a match and replaces it with a specified replacement. Without the g flag, it replaces only the first match. With g, it replaces all matches.

Basic Replacement

const str = 'I like cats. cats are great.';

// Without g: replaces first occurrence only
console.log(str.replace(/cats/, 'dogs'));
// "I like dogs. cats are great."

// With g: replaces all occurrences
console.log(str.replace(/cats/g, 'dogs'));
// "I like dogs. dogs are great."

Special Replacement Patterns

The replacement string supports special character sequences:

PatternInserts
$$Literal $ character
$&The entire matched substring
$`The portion of the string before the match
$'The portion of the string after the match
$1, $2, ...Captured group by number
$<name>Captured group by name
// $&: the full match
const str = 'hello world';
console.log(str.replace(/\b\w/g, '$&!!!'));
// "h!!!ello w!!!orld"

// But more usefully, to wrap matches:
console.log(str.replace(/\w+/g, '[$&]'));
// "[hello] [world]"

Using Captured Groups in Replacement

// Swap first and last name
const name = 'John Smith';
console.log(name.replace(/(\w+) (\w+)/, '$2, $1'));
// "Smith, John"

// Reformat a date
const date = '03/15/2024';
console.log(date.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2'));
// "2024-03-15"

// With named groups
const date2 = '03/15/2024';
console.log(date2.replace(
/(?<month>\d{2})\/(?<day>\d{2})\/(?<year>\d{4})/,
'$<year>-$<month>-$<day>'
));
// "2024-03-15"

Replacement Function

Instead of a string, you can pass a function as the second argument. This function is called for each match, and its return value becomes the replacement:

const str = 'the quick brown fox';
const result = str.replace(/\b\w+/g, (match) => {
return match.charAt(0).toUpperCase() + match.slice(1);
});

console.log(result); // "The Quick Brown Fox"

The replacement function receives these arguments:

ArgumentValue
matchThe full matched substring
p1, p2, ...Captured groups (if any)
offsetIndex of the match in the string
stringThe original string
groupsNamed groups object (if any)
const str = 'border-top-width';

const camelCase = str.replace(/-(\w)/g, (match, letter, offset) => {
console.log(`Match: "${match}", Letter: "${letter}", Offset: ${offset}`);
return letter.toUpperCase();
});

console.log(camelCase);

Output:

Match: "-t", Letter: "t", Offset: 6
Match: "-w", Letter: "w", Offset: 10
borderTopWidth

Practical Example: Template Engine

function renderTemplate(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
if (key in data) {
return data[key];
}
return match; // Leave unresolved placeholders as-is
});
}

const template = 'Hello, {{name}}! You have {{count}} messages from {{sender}}.';
const data = { name: 'Alice', count: 5 };

console.log(renderTemplate(template, data));
// "Hello, Alice! You have 5 messages from {{sender}}."

Practical Example: Escaping HTML

function escapeHTML(str) {
return str.replace(/[&<>"']/g, (char) => {
const entities = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
};
return entities[char];
});
}

console.log(escapeHTML('<script>alert("xss")</script>'));
// "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;"

str.replaceAll(regexp, replacement)

Introduced in ES2021, replaceAll() replaces all occurrences without needing the g flag on string patterns. When used with a regex, the g flag is required.

With Strings

const str = 'aabbcc aabbcc aabbcc';

// replace without g: only first
console.log(str.replace('aa', 'XX'));
// "XXbbcc aabbcc aabbcc"

// replaceAll: all occurrences
console.log(str.replaceAll('aa', 'XX'));
// "XXbbcc XXbbcc XXbbcc"

With Regular Expressions

When using a regex, replaceAll() requires the g flag:

const str = 'foo123bar456baz';

// This works: g flag is present
console.log(str.replaceAll(/\d+/g, '#'));
// "foo#bar#baz"

// This throws a TypeError
try {
str.replaceAll(/\d+/, '#');
} catch (e) {
console.log(e.message);
// "String.prototype.replaceAll called with a non-global RegExp argument"
}

replaceAll() vs. replace() with g

When using regular expressions, replaceAll() with g produces the same result as replace() with g:

const str = 'one-two-three';

// These produce identical results with regex
console.log(str.replace(/-/g, '_')); // "one_two_three"
console.log(str.replaceAll(/-/g, '_')); // "one_two_three"

The real advantage of replaceAll() is with string arguments, where it replaces all occurrences without needing regex:

const str = 'a.b.c.d';

// Awkward: escaping dots in regex
console.log(str.replace(/\./g, '-')); // "a-b-c-d"

// Clean: replaceAll with a simple string
console.log(str.replaceAll('.', '-')); // "a-b-c-d"
tip

Use replaceAll() with plain strings when you want to replace all occurrences of a literal substring. Use replace() with /pattern/g when you need regex pattern matching.

str.split(regexp)

The split() method divides a string into an array of substrings using a separator. When the separator is a regex, you get powerful splitting capabilities.

Basic Usage

// Split by one or more spaces
const str = 'hello world foo';

console.log(str.split(' '));
// ["hello", "", "", "world", "", "", "foo"] (empty strings for extra spaces)

console.log(str.split(/\s+/));
// ["hello", "world", "foo"] (regex handles multiple spaces)

Splitting with Complex Patterns

// Split by multiple delimiters
const str = 'one,two;three:four|five';
console.log(str.split(/[,;:|]/));
// ["one", "two", "three", "four", "five"]

// Split by words, keeping separators... not quite
const sentence = 'Hello World Foo Bar';
console.log(sentence.split(/\s+/));
// ["Hello", "World", "Foo", "Bar"]

Capturing Groups in split()

If the regex contains capturing groups, the captured text is included in the resulting array:

const str = 'one1two2three3four';

// Without capturing group (separators are removed)
console.log(str.split(/\d/));
// ["one", "two", "three", "four"]

// With capturing group (separators are included)
console.log(str.split(/(\d)/));
// ["one", "1", "two", "2", "three", "3", "four"]

This is useful when you need to split a string but also keep track of the delimiters:

const expression = '10+20-30*40';
const parts = expression.split(/([+\-*\/])/);

console.log(parts);
// ["10", "+", "20", "-", "30", "*", "40"]

Limiting the Number of Splits

The optional second argument limits the number of elements returned:

const str = 'a,b,c,d,e';

console.log(str.split(',', 3));
// ["a", "b", "c"]

console.log(str.split(/,/, 2));
// ["a", "b"]

Edge Cases

// Empty string
console.log(''.split(/,/));
// [""] (array with one empty string)

// No matches: returns original string in array
console.log('hello'.split(/,/));
// ["hello"]

// Regex matches at start/end: empty strings in result
console.log(',hello,world,'.split(/,/));
// ["", "hello", "world", ""]

// Splitting every character
console.log('hello'.split(/(?:)/)); // Non-capturing empty match
// ["h", "e", "l", "l", "o"]
warning

Be careful when splitting with a regex that can match an empty string (like /\s*/). This can produce unexpected results with many empty strings in the output.

console.log('abc'.split(/\s*/));
// ["a", "b", "c"] (in most engines, but behavior can vary)

regexp.exec(str): Full Control Over Matching

While the str.match() and str.matchAll() methods are convenient, the exec() method on RegExp objects gives you the most fine-grained control over the matching process. It returns a single match at a time and updates lastIndex, allowing you to step through matches manually.

Without the g Flag

Without g, exec() behaves like match() without g. It always returns the first match, regardless of lastIndex:

const regex = /\d+/;
const str = 'abc 123 def 456';

console.log(regex.exec(str));
// ["123", index: 4, input: "abc 123 def 456", groups: undefined]

// Calling again returns the same result (no progression)
console.log(regex.exec(str));
// ["123", index: 4, ...] (same match)

With the g Flag: Stateful Iteration

With g, each call to exec() starts searching from lastIndex and updates it after a match:

const regex = /\d+/g;
const str = 'abc 12 def 345 ghi 6';

let match;
while ((match = regex.exec(str)) !== null) {
console.log(`Found "${match[0]}" at index ${match.index}, lastIndex now: ${regex.lastIndex}`);
}

Output:

Found "12" at index 4, lastIndex now: 6
Found "345" at index 11, lastIndex now: 14
Found "6" at index 19, lastIndex now: 20

When no more matches are found, exec() returns null and resets lastIndex to 0.

Capturing Groups with exec()

Unlike match() with g, exec() always provides full group information:

const regex = /(\d{4})-(\d{2})-(\d{2})/g;
const str = 'Dates: 2024-03-15 and 2023-12-25';

let match;
while ((match = regex.exec(str)) !== null) {
console.log(`Full: ${match[0]}`);
console.log(`Year: ${match[1]}, Month: ${match[2]}, Day: ${match[3]}`);
console.log(`Index: ${match.index}`);
console.log('---');
}

Output:

Full: 2024-03-15
Year: 2024, Month: 03, Day: 15
Index: 7
---
Full: 2023-12-25
Year: 2023, Month: 12, Day: 25
Index: 22
---

The Infinite Loop Trap

A common and dangerous mistake is using exec() in a loop with a regex that was meant to have the g flag but does not, or creating the regex inside the loop:

// DANGER: Infinite loop: regex without g flag, exec always returns first match
const str = 'abc 123 def 456';
const regex = /\d+/; // No g flag!

let match;
// This loops forever because exec always returns the same first match
// while ((match = regex.exec(str)) !== null) { ... }
// DANGER: Infinite loop: regex created inside loop (new lastIndex each time)
const str = 'abc 123 def 456';

let match;
// Each iteration creates a new regex with lastIndex = 0
// while ((match = /\d+/g.exec(str)) !== null) { ... }

Safe pattern:

// Correct: regex declared outside the loop with g flag
const regex = /\d+/g;
const str = 'abc 123 def 456';

let match;
while ((match = regex.exec(str)) !== null) {
console.log(match[0]); // "123", then "456", then loop ends
}

Manually Controlling lastIndex

You can set lastIndex manually to start searching from a specific position:

const regex = /\w+/g;
const str = 'Hello beautiful world';

regex.lastIndex = 6; // Start after "Hello "
const match = regex.exec(str);

console.log(match[0]); // "beautiful"
console.log(match.index); // 6
console.log(regex.lastIndex); // 15

exec() with the y (Sticky) Flag

With the y flag, exec() matches only at the exact lastIndex position:

const regex = /\d+/y;
const str = '123abc456';

regex.lastIndex = 0;
console.log(regex.exec(str)); // ['123', index: 0, input: '123abc456', groups: undefined]

// lastIndex is now 3
console.log(regex.exec(str)); // null ("abc" at position 3, not digits)

// lastIndex reset to 0
console.log(regex.lastIndex); // 0

exec() with the d (hasIndices) Flag

The d flag (ES2022) adds an indices property to the match result, providing start and end positions for each group:

const regex = /(?<year>\d{4})-(?<month>\d{2})/d;
const str = 'Date: 2024-03';

const match = regex.exec(str);

console.log(match.indices[0]); // [6, 13] (full match start/end)
console.log(match.indices[1]); // [6, 10] (first group (year))
console.log(match.indices[2]); // [11, 13] (second group (month))
console.log(match.indices.groups.year); // [6, 10]
console.log(match.indices.groups.month); // [11, 13]

When to Use exec() vs. matchAll()

const str = 'a1 b2 c3';
const regex = /([a-z])(\d)/g;

// Modern approach (matchAll (cleaner, no state mutation)9
for (const match of str.matchAll(regex)) {
console.log(`${match[1]}=${match[2]}`);
}

// Traditional approach (exec (more control, but stateful)9
let match;
while ((match = regex.exec(str)) !== null) {
console.log(`${match[1]}=${match[2]}`);
}
// Both output: a=1, b=2, c=3
tip

Use matchAll() when you simply need to iterate over all matches. Use exec() when you need manual control over the matching process, such as conditionally stopping early, modifying lastIndex, or using the y flag for positional matching.

regexp.test(str): Boolean Match Test

The test() method is the simplest regex method. It returns true if the pattern matches anywhere in the string, and false otherwise.

Basic Usage

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('not-an-email')); // false
console.log(emailRegex.test('')); // false
const hasDigit = /\d/;

console.log(hasDigit.test('hello')); // false
console.log(hasDigit.test('hello123')); // true
console.log(hasDigit.test('42')); // true

The g Flag Trap with test()

When test() is used with a regex that has the g flag, it updates lastIndex after each call. This leads to alternating results that confuse many developers:

const regex = /hello/g;

console.log(regex.test('hello world')); // true (lastIndex becomes 5)
console.log(regex.test('hello world')); // false (searching from index 5, no match)
console.log(regex.test('hello world')); // true (lastIndex was reset to 0)
console.log(regex.test('hello world')); // false (same cycle)

This is one of the most common regex bugs in JavaScript.

The fix: do not use the g flag with test() unless you intentionally want stateful behavior:

// Correct for simple boolean checks: no g flag
const regex = /hello/;

console.log(regex.test('hello world')); // true
console.log(regex.test('hello world')); // true (always consistent)

Using test() for Validation

test() is ideal for validation functions:

function isValidPassword(password) {
const hasMinLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasDigit = /\d/.test(password);
const hasSpecial = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);

return hasMinLength && hasUppercase && hasLowercase && hasDigit && hasSpecial;
}

console.log(isValidPassword('Hello123!')); // true
console.log(isValidPassword('hello')); // false
console.log(isValidPassword('HELLO123!')); // false (no lowercase)
function isValidHexColor(color) {
return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color);
}

console.log(isValidHexColor('#fff')); // true
console.log(isValidHexColor('#1a2b3c')); // true
console.log(isValidHexColor('#xyz')); // false
console.log(isValidHexColor('123456')); // false (missing #)

test() vs. search() vs. match()

When you only need a boolean answer, test() is the most efficient choice:

const str = 'The year is 2024';
const regex = /\d{4}/;

// All of these check if the pattern exists, but differently:
console.log(regex.test(str)); // true (simplest, most efficient)
console.log(str.search(regex) !== -1); // true (returns index, extra comparison)
console.log(str.match(regex) !== null); // true (creates match object, wasteful)

// For a simple "does it match?" question, always use test()

Complete Method Comparison

Here is a reference table covering all regex-related methods:

MethodCalled OnReturnsFlags AffectedBest For
str.match(regex)StringArray or nullg changes behaviorQuick match: single (no g) or all matches (g)
str.matchAll(regex)StringIteratorRequires gAll matches with full group details
str.search(regex)StringIndex or -1Ignores g, yFinding position of first match
str.replace(regex, repl)StringNew stringg for replace allReplacing first or all matches
str.replaceAll(regex, repl)StringNew stringRequires gReplacing all (clearer intent)
str.split(regex)StringArrayIgnores g, ySplitting by pattern
regex.exec(str)RegExpArray or nullg/y update lastIndexManual iteration, positional matching
regex.test(str)RegExptrue/falseg/y update lastIndexBoolean validation

Decision Guide

Use this flowchart to pick the right method:

  1. Do you only need to know if there is a match? Use regex.test(str)
  2. Do you need the position of the first match? Use str.search(regex)
  3. Do you need the first match with groups? Use str.match(regex) without g
  4. Do you need all matches as simple strings? Use str.match(regex) with g
  5. Do you need all matches with groups? Use str.matchAll(regex)
  6. Do you need to replace matches? Use str.replace() or str.replaceAll()
  7. Do you need to split by a pattern? Use str.split(regex)
  8. Do you need manual control over matching? Use regex.exec(str) with g or y
warning

Be mindful of lastIndex when reusing a regex with the g or y flag across multiple exec() or test() calls on different strings. The stateful nature of lastIndex is the single most common source of regex bugs in JavaScript. Either reset it manually (regex.lastIndex = 0) or create a new regex for each use.

const regex = /\d+/g;

// First string
regex.exec('abc 123'); // match at index 4, lastIndex = 7

// Second string - BUG: lastIndex is still 7 from the previous call
regex.exec('xyz 456'); // null! Searching from position 7 in a shorter string

// Fix: reset lastIndex before reuse
regex.lastIndex = 0;
regex.exec('xyz 456'); // ["456"] (correct)