Regex in JavaScript
The complete guide to using regular expressions in JavaScript -- from basic methods to advanced patterns, with practical examples for real-world development.
Creating Regular Expressions
JavaScript provides two ways to create a regular expression: the literal syntax and the RegExp constructor. Understanding when to use each is the first step to working effectively with regex in JavaScript.
Literal Syntax
The regex literal is enclosed in forward slashes, with optional flags after the closing slash. This is the most common approach and is evaluated at parse time:
const pattern = /\d{3}-\d{3}-\d{4}/g;
// Use it directly
const hasPhone = pattern.test("Call 555-123-4567");
// Or assign and reuse
const phoneRegex = /\d{3}-\d{3}-\d{4}/g;Use the literal syntax when the pattern is known at development time and will not change. It is more readable and slightly faster because the regex is compiled once when the script is parsed.
RegExp Constructor
The RegExp constructor accepts a string pattern and optional flags. Use this when the pattern is dynamic -- built from user input or computed at runtime:
// Dynamic pattern from user input
const userInput = "hello";
const pattern = new RegExp(userInput, "gi");
// Building patterns programmatically
const keywords = ["error", "warning", "fatal"];
const keywordRegex = new RegExp(keywords.join("|"), "gi");
// IMPORTANT: Escape backslashes in the string!
const digitPattern = new RegExp("\\d+", "g"); // Equivalent to /\d+/gThe key gotcha with the constructor is double escaping. Because the pattern is a string, backslashes must be escaped: \\d in a literal becomes "\\\\d" in the constructor. This is a common source of bugs.
Escaping User Input
When building a regex from user input, always escape special characters to prevent regex injection or unintended behavior:
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// Safe usage with user input
const searchTerm = "price: $10.00 (USD)";
const escaped = escapeRegExp(searchTerm);
const regex = new RegExp(escaped, "g");
// regex = /price: \$10\.00 \(USD\)/gString Methods for Regex
JavaScript strings have several methods that accept regex patterns. These are the workhorses of day-to-day regex use.
match()
Returns an array of matches. Behavior depends on the g flag:
const str = "2026-02-09 and 2025-12-25";
// Without g flag: returns first match with groups and index
const first = str.match(/(\d{4})-(\d{2})-(\d{2})/);
// ["2026-02-09", "2026", "02", "09", index: 0, groups: undefined]
// With g flag: returns all matches (no group info)
const all = str.match(/(\d{4})-(\d{2})-(\d{2})/g);
// ["2026-02-09", "2025-12-25"]
// No match: returns null (not an empty array!)
const none = "hello".match(/\d+/);
// nullmatchAll()
Returns an iterator of all matches with full detail (groups, indices). Requires the g flag. This is the modern replacement for the exec() loop pattern:
const str = "2026-02-09 and 2025-12-25";
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;
for (const match of str.matchAll(regex)) {
console.log(match[0]); // "2026-02-09", then "2025-12-25"
console.log(match.groups?.year); // "2026", then "2025"
console.log(match.groups?.month); // "02", then "12"
console.log(match.index); // 0, then 15
}
// Or convert to an array
const matches = [...str.matchAll(regex)];replace() and replaceAll()
Replace matched text with a string or function result:
// Simple replacement
"hello world".replace(/world/, "JS");
// "hello JS"
// Global replacement (replaces ALL occurrences)
"aabbcc".replace(/b/g, "B");
// "aaBBcc"
// replaceAll() - no g flag needed (ES2021+)
"aabbcc".replaceAll("b", "B");
// "aaBBcc"
// Using capture groups in replacement
"John Smith".replace(/(\w+) (\w+)/, "$2, $1");
// "Smith, John"
// Named groups in replacement
"2026-02-09".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<month>/$<day>/$<year>"
);
// "02/09/2026"
// Function replacement for complex logic
"hello world".replace(/\b\w/g, (char) => char.toUpperCase());
// "Hello World"
// Function with full match details
"5 cats and 3 dogs".replace(
/(?<count>\d+) (?<animal>\w+)/g,
(match, count, animal, offset, str, groups) => {
return `${Number(count) * 2} ${animal}`;
}
);
// "10 cats and 6 dogs"search()
Returns the index of the first match, or -1 if none. Similar to indexOf() but accepts regex:
"hello 123 world".search(/\d+/);
// 6
"hello world".search(/\d+/);
// -1split()
Split a string using a regex delimiter. Particularly useful when delimiters vary:
// Split on any whitespace (spaces, tabs, newlines)
"one two\tthree\nfour".split(/\s+/);
// ["one", "two", "three", "four"]
// Split on comma with optional whitespace
"a, b,c , d".split(/\s*,\s*/);
// ["a", "b", "c", "d"]
// Split with capture group (keeps the delimiter)
"one1two2three3four".split(/(\d)/);
// ["one", "1", "two", "2", "three", "3", "four"]RegExp Methods
test()
Returns true or false. The fastest way to check if a pattern matches. Use this when you only need a boolean answer:
const emailRegex = /^[\w.+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
emailRegex.test("user@example.com"); // true
emailRegex.test("not-an-email"); // false
// WARNING: With the g flag, test() is stateful!
const gRegex = /a/g;
gRegex.test("abc"); // true (lastIndex = 1)
gRegex.test("abc"); // false (lastIndex = 0, starts from 1)
gRegex.test("abc"); // true (lastIndex reset to 0)
// Avoid g flag with test() unless you understand lastIndex behaviorexec()
Returns detailed match information or null. With the g flag, successive calls advance through the string (this is the pre-matchAll iteration pattern):
const regex = /\d+/g;
const str = "12 and 34 and 56";
let match;
while ((match = regex.exec(str)) !== null) {
console.log(`Found ${match[0]} at index ${match.index}`);
}
// Found 12 at index 0
// Found 34 at index 7
// Found 56 at index 14
// Modern alternative: use matchAll() instead (see above)Named Capture Groups
Named capture groups (ES2018) make regex code dramatically more readable. Instead of referencing groups by number (match[1]), you reference them by name (match.groups.name):
// Without named groups (hard to read)
const match = "2026-02-09".match(/(\d{4})-(\d{2})-(\d{2})/);
const year = match?.[1]; // "2026" - what does [1] mean?
const month = match?.[2]; // "02"
const day = match?.[3]; // "09"
// With named groups (self-documenting)
const namedMatch = "2026-02-09".match(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
);
const { year, month, day } = namedMatch?.groups ?? {};
// year = "2026", month = "02", day = "09"
// Named groups in replacement strings
"2026-02-09".replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<day>/$<month>/$<year>"
);
// "09/02/2026"
// Named backreferences
/(?<word>\w+)\s\k<word>/.test("hello hello"); // true
/(?<word>\w+)\s\k<word>/.test("hello world"); // falseUnicode Support
JavaScript strings use UTF-16 encoding, which means characters outside the Basic Multilingual Plane (BMP) -- like emoji -- are stored as surrogate pairs. Without the u flag, regex treats these as two separate characters:
// Without u flag: emoji is treated as two "characters"
"I 😀 JS".match(/./g);
// ["I", " ", "\uD83D", "\uDE00", " ", "J", "S"]
// With u flag: emoji is treated as one character
"I 😀 JS".match(/./gu);
// ["I", " ", "😀", " ", "J", "S"]
// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test("😀"); // true
/\p{Script=Greek}/u.test("α"); // true (alpha)
/\p{Letter}/u.test("é"); // true (accented e)
// Common Unicode property escapes
/\p{Lowercase_Letter}/u // Any lowercase letter (any script)
/\p{Uppercase_Letter}/u // Any uppercase letter (any script)
/\p{Number}/u // Any numeric character
/\p{Punctuation}/u // Any punctuation character
/\p{White_Space}/u // Any whitespace characterThe v Flag (ES2024)
The v flag is the successor to u, adding set operations and improved Unicode handling:
// Set subtraction: match letters but NOT vowels
/[[a-z]--[aeiou]]/v.test("b"); // true (consonant)
/[[a-z]--[aeiou]]/v.test("a"); // false (vowel)
// Set intersection: match characters in BOTH sets
/[[a-z]&&[g-p]]/v.test("h"); // true (in both ranges)
/[[a-z]&&[g-p]]/v.test("a"); // false (only in first)
// String properties for multi-character matching
/\p{Basic_Emoji}/v.test("😀"); // truePractical Patterns for JavaScript Developers
Here are battle-tested patterns you can use directly in your JavaScript projects:
// Extract all URLs from text
const urlRegex = /https?:\/\/[\w.-]+(?:\.[a-zA-Z]{2,})[\w\/.-]*/g;
// Parse CSS color values
const hexColor = /#([0-9a-fA-F]{3}){1,2}\b/g;
const rgbColor = /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/g;
// Match template literals / interpolation markers
const templateVar = /\$\{([^}]+)\}/g;
// Validate semantic version
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
// Strip HTML tags
const stripTags = /<[^>]*>/g;
"<p>Hello <b>world</b></p>".replace(stripTags, "");
// "Hello world"
// Match key=value pairs
const keyValue = /(?<key>[\w-]+)\s*=\s*(?<value>"[^"]*"|'[^']*'|[^\s,]+)/g;Performance Tips
Regex performance matters when processing large texts or running patterns in tight loops. Here are the key optimization strategies:
1. Compile Once, Use Many Times
// BAD: Regex is recompiled on every iteration
for (const line of lines) {
if (/error\s+\d+/i.test(line)) { /* ... */ }
}
// GOOD: Compile once outside the loop
const errorRegex = /error\s+\d+/i;
for (const line of lines) {
if (errorRegex.test(line)) { /* ... */ }
}2. Prefer Specific Patterns Over Generic
// SLOW: .* backtracks extensively
/^.*error.*$/m
// FAST: more specific character classes
/^[^\n]*error[^\n]*$/m
// SLOW: nested quantifiers (catastrophic backtracking risk!)
/(a+)+b/
// FAST: flatten the nesting
/a+b/3. Use Non-Capturing Groups When You Don't Need Captures
// Slightly slower: creates unnecessary capture groups
/(foo|bar|baz)+/
// Slightly faster: non-capturing group
/(?:foo|bar|baz)+/4. Use test() Instead of match() for Boolean Checks
// Wasteful: creates a full match array just to check existence
if (str.match(/pattern/)) { /* ... */ }
// Efficient: returns just true/false
if (/pattern/.test(str)) { /* ... */ }5. Be Careful with the g Flag and lastIndex
const regex = /\d+/g;
// lastIndex persists between calls!
regex.test("abc 123"); // true, lastIndex = 7
regex.test("abc 123"); // false, started from lastIndex 7!
// Reset before reuse
regex.lastIndex = 0;
regex.test("abc 123"); // true againCommon Mistakes
These are the most frequent regex bugs in JavaScript code:
- Forgetting that
match()returnsnullon no match. Always check before accessing properties:str.match(pattern)?.[0]. - Stateful
gflag withtest(): ThelastIndexproperty persists between calls. Either reset it or avoid thegflag withtest(). - Double escaping in
RegExpconstructor:new RegExp("\\d+")matches "d", not digits. Usenew RegExp("\\\\d+"). - Not escaping user input: Characters like
.,*,+are regex metacharacters. Always escape dynamic strings before passing them toRegExp. - Assuming
.matches newlines: By default,.does not match\n. Use thes(dotAll) flag or[\\s\\S]as an alternative.
Try all of these JavaScript regex patterns with our Regex Tester -- it uses the same JavaScript RegExp engine that runs in your browser, so what you test here is exactly what you get in your code.
Further Reading
- MDN RegExp
Complete MDN reference for JavaScript RegExp object and methods.
- ECMAScript specification — RegExp
The authoritative specification for JavaScript regex semantics.
- V8 blog — Irregexp
How the V8 engine compiles and optimizes regular expressions.