RegExp
Learn how to build regular expression patterns in JavaScript and use them to test, match, and replace text with the RegExp object and its related string methods.
A regular expression (regex) is a mini-language for describing patterns in text — instead of searching for one exact string, you describe a shape ("a sequence of digits", "an email-like string", "anything that starts with a capital letter") and let the engine find it for you.
Creating a regular expression: literals vs the constructor
JavaScript gives you two ways to create a RegExp object. The regex literal is the most common: you write the pattern between forward slashes, like /abc/. It is parsed at script-load time, so it is fast and convenient for patterns you know in advance.
The RegExp constructor, new RegExp("abc"), takes the pattern as a string instead. This is the form you must use when the pattern itself is built dynamically at runtime — for example, from user input or a variable — since you cannot interpolate a variable inside a literal's slashes.
• Literal: const re = /hello/i; • Constructor: const re = new RegExp("hello", "i"); • Constructor from a variable: const re = new RegExp(searchTerm, "g");
Flags
Flags change how a pattern is applied and are written after the closing slash (or as the second argument to the constructor). The most commonly used flags are i, g, m, and s.
Flags can be combined: /foo/gi matches every occurrence of "foo" regardless of case. Forgetting the g flag is one of the most common regex bugs — without it, methods like replace() and matchAll()-style iteration will only touch the first match.
• i — case-insensitive matching • g — global: find all matches, not just the first • m — multiline: ^ and $ match the start/end of each line, not just the whole string • s — dotAll: . also matches newline characters • u — treats the pattern as full Unicode code points • y — sticky: matches only starting at lastIndex
Character classes and quantifiers
A character class matches one character from a set. Square brackets define a custom set, like [aeiou] for any vowel, while several classes have shorthand escapes: \d (a digit, same as [0-9]), \w (a word character: letters, digits, underscore), and \s (any whitespace character). Capitalizing the letter negates it — \D, \W, and \S match the opposite.
A quantifier says how many times the preceding token may repeat. * means zero or more, + means one or more, ? means zero or one (optional), and {n,m} means between n and m times. Quantifiers are greedy by default (they grab as much as possible); adding a ? after them, like *?, makes them lazy (they grab as little as possible).
• \d+ — one or more digits, e.g. matches "2026" or "7" • [A-Za-z]{3,5} — 3 to 5 letters • colou?r — matches both "color" and "colour" • <.+?> — lazily matches the shortest possible tag-like text
Anchors, groups, and capture groups
Anchors don't match characters — they match positions. ^ anchors to the start of the string (or line, with the m flag), and $ anchors to the end. \b matches a word boundary, useful for matching whole words rather than substrings inside longer words.
Parentheses ( ) create a group, which does two things at once: it lets you apply a quantifier to a whole sequence (like (ab)+), and it captures the matched text so you can extract it later from a match result or reference it in a replacement. A (?:...) non-capturing group groups without capturing, which is useful when you need grouping for a quantifier but do not need the text back.
• ^\d{3}-\d{4}$ — matches a whole string that is exactly a phone-like "123-4567" pattern • \bcat\b — matches "cat" as a whole word, not the "cat" inside "category" • (\d{4})-(\d{2})-(\d{2}) — captures year, month, and day as three separate groups from a date string • (?:https?:\/\/)?example\.com — groups the optional protocol without capturing it
Using patterns: test, match, and replace
regex.test(str) returns a plain true or false, and is the right tool when you only need to know whether a pattern matches — it's the classic choice for form validation.
str.match(regex) (without the g flag) returns an array with the full match plus any capture groups and their index; with the g flag it instead returns an array of every full match, with no capture group detail. str.replace(regex, replacement) swaps matched text for something else, and the replacement string can reference captured groups with $1, $2, and so on. A regex with the g flag replaces every match; without it, replace() stops after the first.
RegExp literal syntax
Inside a literal, a forward slash inside the pattern must be escaped as \/. Inside a constructor string, backslashes must be doubled (\\d instead of \d) because the string itself processes escape sequences first. Special characters that need escaping to match literally include . * + ? ^ $ { } ( ) | [ ] \ — precede any of them with a backslash to match them as-is.
const pattern = /abc[0-9]+\s?xyz/gi;
// ^literal ^flags
const re = new RegExp("abc[0-9]+\\s?xyz", "gi"); // equivalent, from a stringCode Examples
test() is the simplest way to ask a yes/no question about a string: does this pattern occur anywhere inside it? Expected result: true false
\d+ matches one or more digit characters found anywhere in the string.
Anchoring with ^ and $ forces the whole string to match the pattern, not just some part of it — essential for validation so that 'bad name!' does not partially match and slip through. Expected result: true false false
^[A-Za-z0-9_]{3,16}$ requires the ENTIRE string to consist of 3-16 allowed characters.
Common mistake: Leaving off ^ and $ would let isValidUsername("bad name!") return true, because /[A-Za-z0-9_]{3,16}/ would still find a valid-looking chunk somewhere inside the string.
matchAll() (paired with the g flag) returns an iterator of full match objects, each still carrying its own capture groups — unlike match() with g, which discards them. replace() then reuses those same groups via $1, $2, $3 to rearrange the date format. Expected result: Found 2019-03-14 -> year 2019, month 03, day 14 Found 2023-11-02 -> year 2023, month 11, day 02 Founded: 14/03/2019, Relaunched: 02/11/2023
$3/$2/$1 in the replacement string reorders the captured day, month, and year groups.
const hasDigits = /\d+/;
console.log(hasDigits.test("Order #4521")); // true
console.log(hasDigits.test("No numbers here")); // falsefunction isValidUsername(name) {
// 3 to 16 characters: letters, digits, and underscores only
const pattern = /^[A-Za-z0-9_]{3,16}$/;
return pattern.test(name);
}
console.log(isValidUsername("eh_dev_01")); // true
console.log(isValidUsername("ab")); // false, too short
console.log(isValidUsername("bad name!")); // false, space and ! not allowedconst dateText = "Founded: 2019-03-14, Relaunched: 2023-11-02";
const dateRe = /(\d{4})-(\d{2})-(\d{2})/g;
const matches = [...dateText.matchAll(dateRe)];
for (const m of matches) {
console.log(`Found ${m[0]} -> year ${m[1]}, month ${m[2]}, day ${m[3]}`);
}
const reformatted = dateText.replace(dateRe, "$3/$2/$1");
console.log(reformatted);Anatomy of a pattern
A regex literal is delimited by slashes, its body describes the shape to match, and flags after the closing slash change matching behavior globally.
/ ^(\\d{3})-(\\d{4})$ / g i
| | | | | |
slash | group 2 slash flags
group 1 captured end
captured of
patternImportant Rules
Regex literals (/pattern/flags) are for patterns known ahead of time; new RegExp(string, flags) is for patterns built dynamically at runtime.
Without the g flag, replace() and match() only touch the first match in the string.
^ and $ anchor to the start and end — always include them for full-string validation, or a pattern can match a small piece and let bad input through.
Parentheses both group and capture; use (?:...) when you need grouping without capturing.
Where this shows up
Regular expressions are everywhere in real applications: validating emails, phone numbers, and passwords on signup forms; parsing log files to pull out timestamps and error codes; stripping or masking sensitive data before logging it; and search-and-replace tooling in code editors and linters, which are themselves built on regex engines.
const isNumber = /[0-9]+/; isNumber.test("abc123"); // true — unexpectedly passes const isNumber = /^[0-9]+$/; isNumber.test("abc123"); // false
Without ^ and $, the pattern is satisfied by finding digits ANYWHERE in the string, so "abc123" still matches because of the "123" part. Anchoring forces the whole string to be digits.
str.replace(/cat/, "dog"); // only replaces the first "cat" str.replace(/cat/g, "dog"); // replaces every "cat"
Forgetting the g flag is one of the most common regex mistakes — replace() (and similar operations) stop after the first match unless global matching is turned on.
[Multiple Choice] Which flag makes a regular expression find ALL matches in a string instead of stopping after the first one? A. i B. g C. m D. s
B. g — The g (global) flag tells methods like replace() and matchAll() to continue past the first match and find every occurrence in the string.
[True / False] /[0-9]+/.test("price: 42") returns false because the string is not entirely made of digits. A. True B. False
B. False — False. Without ^ and $ anchors, test() only checks whether the pattern occurs ANYWHERE in the string, and "price: 42" does contain digits, so it returns true.
Write a function isValidHexColor(str) that returns true only for strings like "#fff" or "#a1b2c3" (a # followed by exactly 3 or exactly 6 hexadecimal digits), and false for anything else.
function isValidHexColor(str) { return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(str); } console.log(isValidHexColor("#fff")); // true console.log(isValidHexColor("#a1b2c3")); // true console.log(isValidHexColor("#12345")); // false console.log(isValidHexColor("fff")); // false
Hexadecimal digits are 0-9 and a-f (case-insensitive). Use alternation inside the pattern to allow either 3 or 6 digits, and remember to anchor the whole string.