Skip to main content
Regex Guide

Regex in JavaScript
Every Method Explained with Examples

Pick the right method, read the return value, and never hit a mysterious null again.

10 min read·Updated May 2026

Regex in JavaScript gives you six distinct methods across RegExp and String, each returning something different — and picking the wrong one is the most common source of "why did my regex return null?" bugs.

Quick-Reference: All Seven Methods

Before diving in, here is every regex-aware method at a glance — the object it lives on, whether it needs the /g flag, and what it hands back.

RegExp test(str)
→ boolean
Does the pattern match? Nothing else.
RegExp exec(str) /g required
→ array | null
Low-level; stateful with /g. Prefer matchAll().
String match(regex)
→ array | null
First match + groups; or all full matches with /g.
String matchAll(regex) /g required
→ iterator
All matches with capture groups. Requires /g.
String replace(regex, x)
→ new string
Replace first match. Replace all with /g.
String replaceAll(regex, x) /g required
→ new string
Replace all matches. Requires /g.
String split(regex)
→ array
Split string on pattern as delimiter.

test() — The Simplest Check

RegExp.test(str) returns true or false. Use it whenever you only need to know whether a match exists.

// Basic use — just need a boolean
const isHex = /^#[0-9a-f]{6}$/i;
isHex.test('#1a2b3c');  // true
isHex.test('red');      // false

// ⚠ Stateful lastIndex bug — DON'T do this
const re = /\d+/g;
['abc', '123', '456'].filter(s => re.test(s));
// Returns ['123'] — '456' is missed because lastIndex is non-zero!

// ✓ Fix 1 — drop the /g flag when you don't need it
const re2 = /\d+/;
['abc', '123', '456'].filter(s => re2.test(s));
// Returns ['123', '456'] — correct

// ✓ Fix 2 — reset lastIndex before each call if /g is genuinely needed
re.lastIndex = 0;
re.test('next string');

match() — With and Without /g

String.match(regex) behaves very differently depending on whether the /g flag is present. Without /g you get the first match plus capture groups. With /g you get all full matches — but capture groups are silently dropped.

const str = 'prices: $12.50 and $7.99';

// WITHOUT /g — first match + capture groups
str.match(/\$(\d+\.\d{2})/);
// ['$12.50', '12.50', index: 8, input: '...', groups: undefined]
//   ↑ full match   ↑ group 1

// WITH /g — all full matches, capture groups gone
str.match(/\$(\d+\.\d{2})/g);
// ['$12.50', '$7.99']  ← no group 1 values

// Returns null when there is no match — always guard it
const m = str.match(/€\d+/);
if (m) { /* safe to use m[0], m[1] etc. */ }

matchAll() — All Matches With Capture Groups

String.matchAll(regex) fixes the biggest frustration with match(): it preserves capture groups for every match, not just the first. It requires the /g flag and returns a lazy iterator.

const str = '2026-01-15 and 2026-03-22';
const re  = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;

// Spread into an array
const matches = [...str.matchAll(re)];
matches[0][0]              // '2026-01-15'  full match
matches[0][1]              // '2026'        group 1
matches[0].groups.month    // '01'          named group

// Iterate with destructuring — cleaner
for (const m of str.matchAll(re)) {
  const { year, month, day } = m.groups;
  console.log(`${year}/${month}/${day}`);
}
// 2026/01/15
// 2026/03/22

exec() — Low-Level and Stateful

RegExp.exec(str) is the oldest regex method in JavaScript. Each call returns one match (the next one when /g is set), or null when exhausted. It predates matchAll() and the classic use-case is a while loop.

const str = 'cat bat sat';
const re  = /[a-z]at/g;
let m;

// Classic while-loop pattern
while ((m = re.exec(str)) !== null) {
  console.log(m[0], 'at index', m.index);
}
// cat at index 0
// bat at index 4
// sat at index 8

// ✓ Prefer matchAll() for modern code — same result, cleaner
for (const m of str.matchAll(/[a-z]at/g)) {
  console.log(m[0], 'at index', m.index);
}

replace() and replaceAll()

Both methods return a new string — the original is never mutated. The replacement argument can be a string (with backreferences) or a function called for each match.

// Replace first vs all
'aabbaa'.replace(/a/,  'x')   // 'xabbaa'  — first only
'aabbaa'.replace(/a/g, 'x')   // 'xxbbxx'  — all
'aabbaa'.replaceAll(/a/g, 'x') // 'xxbbxx'  — all (requires /g)

// Backreferences $1 $2 in the replacement string
'2026-05-02'.replace(
  /(\d{4})-(\d{2})-(\d{2})/,
  '$3/$2/$1'
);
// '02/05/2026'

// $& inserts the entire matched substring
'hello world'.replace(/\w+/g, '[$&]');
// '[hello] [world]'

// Replacement function — full control per match
'prices: $12.50 and $7.99'.replace(
  /\$(\d+\.\d{2})/g,
  (match, p1, offset) => {
    const doubled = (parseFloat(p1) * 2).toFixed(2);
    return `$${doubled}`;
  }
);
// 'prices: $25.00 and $15.98'

split() — Splitting on a Pattern

String.split(regex) treats every match as a delimiter. Capture groups inside the regex are included in the output array — useful when you need to keep the delimiters.

// Split on one or more whitespace characters
'  foo   bar  baz  '.split(/\s+/);
// ['', 'foo', 'bar', 'baz', '']  ← empty strings at edges

// Split on any punctuation between words
'one,two;three four'.split(/[,;\s]+/);
// ['one', 'two', 'three', 'four']

// Capture groups are included in the result
'2026-05-02'.split(/(-)/);
// ['2026', '-', '05', '-', '02']
//          ↑ the delimiter itself is kept

Flags Reference

Flag Name Effect
/g global Find all matches; makes exec()/test() stateful via lastIndex.
/i ignoreCase Case-insensitive matching — A matches a, Z matches z.
/m multiline ^ and $ match start/end of each line, not just the full string.
/s dotAll . matches any character including newline \n (ES2018).
/u unicode Full Unicode mode; required for \p{…} property escapes and astral codepoints.
/d hasIndices Adds .indices property to matches with start/end positions of each group (ES2022).

Common Mistakes

Forgetting /g for matchAll() and replaceAll()
Both matchAll() and replaceAll() throw a TypeError if you pass a regex without the /g flag. Always include it: /pattern/g.
The stateful lastIndex trap with /g and exec() / test()
A regex with /g stores its position in lastIndex. Reusing the same regex object across multiple strings (e.g. inside a .filter() callback) means each call starts where the last one stopped. Drop /g when you only need a boolean, or reset regex.lastIndex = 0 before each use.
match() with /g drops capture groups
str.match(/(\d+)/g) returns ['1', '2'] — the capture group values are gone. If you need both all matches and their groups, use matchAll().
Double-escaping backslashes in new RegExp()
In a string you must write '\\d' to produce the regex \d. The string '\d' is just 'd' to the regex engine. Prefer regex literals /\d/ whenever the pattern is known at write-time; only use new RegExp() for dynamic patterns.

Test Your Regex in Real Time

Paste any pattern from this page into the Regex Tester — see matches, capture groups, and flags highlighted instantly, all in your browser.

Open Regex Tester →

Related