jsguides

RegExp

RegExp is a built-in object that represents regular expressions for pattern matching and text manipulation. Regular expressions are powerful tools for searching, replacing, and extracting text based on patterns.

Creating RegExp objects

Create a RegExp using the constructor or a literal notation:

// Literal notation (preferred)
const regex1 = /pattern/;
const regex2 = /pattern/flags;

// Constructor
const regex3 = new RegExp("pattern");
const regex4 = new RegExp("pattern", "flags");

Flags

Flags modify how the regular expression behaves:

FlagDescription
gGlobal — find all matches
iCase-insensitive matching
mMultiline — ^ and $ match line boundaries
sDotall — . matches newlines
uUnicode — enable full Unicode support
ySticky — match only from lastIndex
const caseInsensitive = /hello/i;
const globalMatch = /o/g;
const multiline = /^line/m;

Every flag changes how the engine interprets the pattern — g finds all occurrences instead of stopping at the first match, i ignores letter case, m makes anchors work per line, and s lets . cross newline boundaries. The character class shortcuts below are the most common tokens you’ll use with these flags day to day.

Basic Patterns

Character Classes

/\d/       // Matches any digit [0-9]
/\D/       // Matches any non-digit
/\w/       // Matches word character [a-zA-Z0-9_]
/\W/       // Matches non-word character
/\s/       // Matches whitespace
/\S/       // Matches non-whitespace
/[abc]/    // Matches any character in brackets
/[^abc]/   // Matches any character NOT in brackets
/[a-z]/    // Matches any character in range

Character classes are the building blocks of patterns, but they match anywhere in the string unless you constrain them. Anchors let you control where a match can occur by pinning the pattern to specific positions like the start or end of input.

Anchors

/^hello/   // Matches at start
/world$/  // Matches at end
/\bword/  // Matches at word boundary
/\Bword/  // Matches at non-word boundary

Anchors set the position but don’t consume characters. Quantifiers determine how many times the preceding token repeats, letting you express flexible repetition without writing the same pattern over and over. They’re essential for matching variable-length input like phone numbers or identifiers where the exact size isn’t fixed.

Quantifiers

/a*/      // Zero or more
/a+/      // One or more
/a?/      // Zero or one
/a{3}/    // Exactly 3
/a{2,}/   // 2 or more
/a{2,5}/  // Between 2 and 5

Quantifiers apply to the token directly before them, so /a+/ means “one or more a characters” while /(ab)+/ matches repeating groups. When you need to match a literal character that also has special meaning in regex syntax — a dot, a backslash, or a newline — you escape it with a backslash. The escape characters below are the most frequently needed.

Special Characters

/\./      // Escaped dot (matches literal .)
/\n/      // Newline
/\t/      // Tab
/\r/      // Carriage return
/\\/      // Backslash

Methods

test()

The simplest way to check a pattern — test() answers “does this string contain the pattern?” with a boolean. It’s the fastest RegExp method and the one you’ll reach for in if statements and filter callbacks when you only care about presence, not position or capture groups.

Tests if a pattern exists in a string. Returns true or false:

const regex = /hello/;
regex.test("hello world");  // true
regex.test("goodbye");     // false

test() is a lightweight yes/no check — perfect for conditionals and validation functions. When you need the actual match data (the matched text, capture groups, or the match position), use exec() instead.

exec()

While test() only tells you whether a pattern matched, exec() returns the actual match data — the full matched string plus each capture group. It’s the method you reach for when you need to extract structured information from text, like dates, URLs, or identifiers. Each call returns either a match array with indexed groups and a position or null when nothing matches.

Executes a search and returns an array with match details or null:

const regex = /(\d+)-(\d+)-(\d+)/;
const result = regex.exec("2024-03-16");

result[0];      // "2024-03-16" (full match)
result[1];      // "2024" (first capture group)
result[2];      // "03" (second capture group)
result[3];      // "16" (third capture group)
result.index;   // 0 (position of match)

String Methods

match()

After exec(), the string-side counterpart is match(), which runs the regex against a string and returns matches. Its behavior depends on whether the regex carries the g flag — without it you get a detailed match object with index and input, and with it you get a flat array of all matching substrings.

Returns matches found in a string:

"hello world".match(/o/g);    
// with g flag: ["o", "o"]

"hello".match(/o/);          
// without g flag: ["o", index: 4, input: "hello"]

With the g flag, match() returns just the matched strings. Without g, it returns the full match object with index and input. When you need capture groups from every match in a global search, matchAll() gives you an iterator instead of a flat array.

matchAll()

When you have a global regex with capture groups, match() drops the group information and returns only the full matches. matchAll() preserves capture groups for every match by returning an iterator instead of a flat array — essential when processing structured records like log lines or CSV rows.

Returns an iterator of all matches with capture groups:

const regex = /(\d+)-(\d+)/g;
const matches = "12-34 56-78".matchAll(regex);

for (const match of matches) {
  console.log(match[0], match[1], match[2]);
}
// "12-34" "12" "34"
// "56-78" "56" "78"

matchAll() shines when every match contains structured data from capture groups. Once you have identified the text to change, replace() lets you swap it out — and it can use the same capture group references in the replacement string.

replace()

Once you’ve found a pattern, the natural next step is to substitute it. replace() accepts a regex or string as the search term and a replacement string — which can reference capture groups with $1, $2, and so on. This turns a pattern match directly into a reformatting operation.

Replaces matched text:

"hello world".replace(/world/, "there");
// "hello there"

"cat cat cat".replace(/cat/g, "dog");
// "dog dog dog"

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

replace() with capture groups turns pattern matching into a formatting tool. If you need to break a string apart at pattern boundaries instead, split() uses the same regex engine but in reverse — it removes the separator and returns the pieces.

split()

When the goal is decomposition rather than substitution, split() breaks a string into an array at every point where a pattern matches. Unlike splitting on a fixed character, a regex split handles variable delimiters — whitespace of any length, commas with optional spaces, or numeric separators that differ across lines.

Splits string by pattern:

"a,b,c".split(/,/);
// ["a", "b", "c"]

"one1two2three".split(/\d/);
// ["one", "two", "three"]

split() with a regex pattern handles separators that aren’t a single fixed string — useful when the delimiter varies slightly. If you only need to know where a match appears rather than extracting it, search() gives you the index without building a match array.

If you only need the position where a match starts — a quick “is it there and where?” check — search() is lighter than exec() and match(). It returns the index of the first match or -1 when nothing is found, making it useful for guard clauses that route execution based on whether text contains a particular pattern.

Returns index of first match or -1:

"hello world".search(/world/);  // 6
"hello".search(/xyz/);          // -1

The methods above cover the individual operations. The following examples combine them to solve real problems — validating input, pulling out specific values, and enforcing multi-rule constraints on a single string. Each builds on test(), match(), or replace() to handle a common task.

Practical Examples

Email Validation

function isValidEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

isValidEmail("user@example.com");  // true
isValidEmail("invalid");            // false

Validation returns a boolean — pass or fail. Often you also want to pull structured data out of free-form text. A capture group with a numeric pattern can grab the value while ignoring the surrounding label.

Extracting Numbers

Validation tells you whether input is acceptable; extraction pulls usable data out of it. A capture group paired with match() isolates a numeric value from surrounding text, letting you convert the string result into a number that’s ready for arithmetic or storage.

const text = "Price: $19.99";
const match = text.match(/\d+\.\d+/);

if (match) {
  console.log(parseFloat(match[0]));  // 19.99
}

Extraction turns a pattern match into a typed value you can compute with. Validation often chains multiple regex checks to verify that a single input satisfies several independent rules at once, which is a common pattern for password and form constraints.

Password Strength

Chaining multiple test() calls is a readable way to enforce several independent rules on the same input. Each regex checks one dimension — length, case variety, and digit presence — and the && chain fails fast if any rule isn’t satisfied.

function isStrongPassword(password) {
  return /.{8,}/.test(password) &&    // At least 8 chars
         /[A-Z]/.test(password) &&     // Uppercase
         /[a-z]/.test(password) &&     // Lowercase
         /[0-9]/.test(password);       // Number
}

Literal notation vs constructor

Prefer the literal form (/pattern/flags) for static patterns — it is more concise and the pattern is compiled once when the script is parsed. Use the constructor (new RegExp(string, flags)) when the pattern needs to be built dynamically from variables:

const field = "email";
const pattern = new RegExp(`\\b${field}\\b`, "i");
// /\bemail\b/i — built from a variable

Note that backslashes must be doubled in the string form ("\\d" becomes \d in the regex), whereas in a literal they are single (/\d/). This doubling is a common source of bugs when moving a pattern from literal to constructor form, so double-check escape sequences during the conversion.

The global flag and stateful lastIndex

When the g (global) or y (sticky) flag is used, a RegExp object is stateful. Each call to exec() or test() advances the lastIndex property to the position after the last match. The next call starts from that position. This means calling test() in a loop with the same regex object will produce alternating true/false results if the pattern matches — a common bug.

const re = /a/g;
re.test("banana"); // true (lastIndex → 1)
re.test("banana"); // true (lastIndex → 3)
re.test("banana"); // true (lastIndex → 5)
re.test("banana"); // false — no match after index 5, resets lastIndex to 0
re.test("banana"); // true again

To avoid this, either create a new RegExp per call or reset re.lastIndex = 0 before reusing it.

Named capture groups

ES2018 added named capture groups, which let you refer to match results by name instead of index. When a regex has three or more groups, numeric indices become hard to track — groups.year is far more readable than result[1]. Named groups also survive pattern edits better, since adding a new group at the front doesn’t shift all the existing number references.

const date = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { groups } = date.exec("2026-03-16");
groups.year;  // "2026"
groups.month; // "03"
groups.day;   // "16"

Named groups are also available in String.prototype.replace() as $<name> references, making date and string reformatting readable without numeric capture group indices.

See Also