Regular expressions in JavaScript

Describe text patterns precisely, test and replace with them, pull data out with named groups, and know when not to use them.

  • Beginner
  • 9 min read
  • Updated 17 Oct 2026

What you will learn

  • Write and test a pattern
  • Extract data with named groups
  • Choose the right flags
  • Recognise when a regex is the wrong tool

Before you start

A regular expression, or regex, is a small pattern language for describing text, such as "four digits, a hyphen, two digits". JavaScript uses regexes to test, search, split and replace strings. They are compact and easy to get wrong, so every pattern here is shown with inputs it does and does not match, and the lesson ends with cases where a regex is the wrong tool.

Creating a regex

There are two ways to create one:

JavaScript
const literal = /colou?r/i;                // regex literal
const built = new RegExp("colou?r", "i");  // constructor, from a string

Use the literal form when the pattern is fixed. Use new RegExp() when you build the pattern from a variable. In the string form, every backslash must be doubled, because the string itself uses backslash escapes:

JavaScript
const digits = new RegExp("\\d+");
console.log(digits.test("abc 42")); // → true

Testing for a match

regex.test(string) returns true or false:

JavaScript
const colour = /colou?r/i;

console.log(colour.test("Colour")); // → true
console.log(colour.test("color"));  // → true
console.log(colour.test("colr"));   // → false

u? means "an optional u", and the i flag makes the match case-insensitive. Notice that test looks for the pattern anywhere in the string, so /colou?r/ also matches "watercolours". To match the whole string, anchor it with ^ (start) and $ (end).

Pattern syntax

Characters and character classes

PatternMatches
abcThe literal characters abc
.Any single character except a line break
[aeiou]One character from the set
[a-z0-9]One character in a range
[^0-9]One character not in the set
\dA digit, 0 to 9
\wA "word" character: A to Z, a to z, 0 to 9 and _
\sWhitespace: space, tab, line break and similar
\D, \W, \SThe opposite of \d, \w, \s
\bA word boundary (a position, not a character)

To match a character that has a special meaning, such as ., *, ?, ( or /, escape it with a backslash: \. matches a full stop.

\w only covers ASCII letters, so it does not match "é" or "ö"; the flags section shows the fix.

Anchors

PatternMatches
^The start of the string (or of a line with the m flag)
$The end of the string (or of a line with the m flag)
\bA boundary between a word character and a non-word character
JavaScript
console.log(/\bcat\b/.test("the cat sat")); // → true
console.log(/\bcat\b/.test("concatenate")); // → false

Quantifiers

PatternMeans
?Zero or one
*Zero or more
+One or more
{3}Exactly three
{2,4}Between two and four
{2,}Two or more

A quantifier applies to the item immediately before it: one character, one class or one group.

Groups and alternation

Parentheses group part of a pattern so a quantifier can apply to the whole group, and they capture what that part matched. (?:...) groups without capturing. A vertical bar means "or".

Here is a pattern for the URL slugs this course uses: lowercase letters and digits, separated by single hyphens:

JavaScript
const slug = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

console.log(slug.test("javascript-promises")); // → true
console.log(slug.test("css-grid-2"));          // → true
console.log(slug.test("JavaScript"));          // → false (capitals)
console.log(slug.test("trailing-"));           // → false
console.log(slug.test("double--dash"));        // → false
console.log(slug.test("has space"));           // → false

Read it piece by piece: ^ start, [a-z0-9]+ one or more lowercase letters or digits, (?:-[a-z0-9]+)* zero or more "hyphen plus letters or digits" groups, $ end. Every hyphen must be followed by a character, so trailing and double hyphens fail.

Greedy and lazy quantifiers

Quantifiers are greedy: they match as much as they can. Adding ? after a quantifier makes it lazy, matching as little as possible:

JavaScript
const text = 'say "hi" and "bye"';

console.log(text.match(/".*"/)[0]);  // → "hi" and "bye"
console.log(text.match(/".*?"/)[0]); // → "hi"

The greedy .* ran to the last quote; the lazy .*? stopped at the first one that completed the match. A negated class, /"[^"]*"/, is often clearer still.

Flags

Flags go after the closing slash, or as the second argument to new RegExp().

FlagEffect
gGlobal: find every match, not just the first
iCase-insensitive
mMultiline: ^ and $ match at the start and end of each line
s"dotAll": . also matches line breaks
uUnicode: treat the pattern and input as Unicode code points, and allow \p{...}
vAn upgraded u mode with set operations in classes (ES2024, supported in all current browsers)
ySticky: match only at the position in lastIndex
dRecord the start and end index of each captured group

The u flag enables Unicode property escapes, which are the reliable way to match letters in any language:

JavaScript

Finding matches

match without g: the first match and its groups

JavaScript
const date = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = "Updated on 2026-09-14".match(date);

console.log(result[0]);          // → 2026-09-14 (the whole match)
console.log(result.index);       // → 11 (where it starts)
console.log(result.groups.year); // → 2026
console.log(result.groups.day);  // → 14
console.log(result[1]);          // → 2026 (groups are also numbered)

(?<year>...) is a named capturing group. Names make the pattern self-documenting and let you read results with result.groups.year instead of counting brackets. If there is no match, match returns null, so check before reading properties, or use optional chaining: text.match(date)?.groups.year.

The dashboard's updated dates use this format. For real date arithmetic, though, use Date or Intl from lesson 27.

match with g: every match, no groups

JavaScript
const text = "Tagged #javascript, #async and #css.";

console.log(text.match(/#\w+/g)); // → [ '#javascript', '#async', '#css' ]

With g, match returns just the matched strings, without group details, or null when nothing matches.

matchAll: every match with its groups

JavaScript
for (const match of text.matchAll(/#(\w+)/g)) {
  console.log(match[1], match.index);
}
// → javascript 7
// → async 20
// → css 31

matchAll requires the g flag (it throws a TypeError without it) and returns an iterator of full match objects.

search and split

string.search(regex) returns the index of the first match, or -1. string.split() accepts a regex as the separator:

JavaScript
console.log("html, css ,javascript".split(/\s*,\s*/));
// → [ 'html', 'css', 'javascript' ]

Replacing

replace swaps the first match; with the g flag, or with replaceAll, it swaps every match:

JavaScript
console.log("  too   many   spaces ".trim().replace(/\s+/g, " "));
// → too many spaces

In the replacement string, $1, $2 and so on insert numbered groups, and $<name> inserts a named group:

JavaScript
const isoDate = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g;

console.log("2026-09-14 and 2025-01-02".replace(isoDate, "$<day>/$<month>/$<year>"));
// → 14/09/2026 and 02/01/2025

You can also pass a function that receives the match and returns the replacement. replaceAll with a regex requires the g flag.

Building a pattern from user input

The dashboard's search box could build a regex from what the user types. But characters such as +, ( and . have special meanings, so a search for "c++" produces an invalid pattern:

JavaScript
new RegExp("c++"); // SyntaxError: Invalid regular expression: /c++/: Nothing to repeat

Escape user input before putting it in a pattern:

JavaScript
function escapeRegExp(text) {
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

const term = "c++";
const search = new RegExp(escapeRegExp(term), "i");

console.log(search.source);                  // → c\+\+
console.log(search.test("Learn C++ today")); // → true

$& in the replacement inserts the whole match, so every special character gets a backslash in front. For a plain "contains" search, though, you do not need a regex at all: title.toLowerCase().includes(term.toLowerCase()) is simpler and cannot fail.

The lastIndex trap

A regex with the g or y flag remembers where its last match ended in its lastIndex property, and test and exec start searching from there:

JavaScript
const re = /css/g;

console.log(re.test("css")); // → true  (lastIndex is now 3)
console.log(re.test("css")); // → false (searched from index 3, found nothing, reset to 0)
console.log(re.test("css")); // → true

This bites when one global regex tests many strings, for example inside a filter. Do not use the g flag with test; keep it for match, matchAll, replace and replaceAll.

When a regex is the wrong tool

Regexes describe patterns in flat text. Many jobs look like that but are not:

TaskUse instead
Parsing HTMLDOMParser, or DOM methods on elements you already have
Parsing URLs and query stringsnew URL() and URLSearchParams
Parsing JSONJSON.parse()
Checking if a string starts or ends with somethingstartsWith(), endsWith(), includes()
Validating email addresses fullytype="email" and a confirmation email; a regex can only reject obvious mistakes
Dates and numbers for displayIntl.DateTimeFormat, Intl.NumberFormat

Some patterns take exponential time on certain inputs. Nested quantifiers such as (a+)+ or (\w+\s?)* followed by something that fails to match can make the engine try a huge number of combinations, freezing the page or a server. This is called catastrophic backtracking, and attackers exploit it with crafted input (a "ReDoS" attack). Keep patterns simple, avoid nested quantifiers, and limit input length before matching.

Summary

  • Create regexes with /pattern/flags, or new RegExp(string, flags) with doubled backslashes.
  • Use character classes ([a-z], \d, \w, \s), anchors (^, $, \b), quantifiers (?, *, +, {n,m}) and groups to build patterns.
  • Quantifiers are greedy; add ? for lazy matching.
  • test checks; match and matchAll extract; replace and replaceAll rewrite; split splits.
  • Named groups ((?<name>...)) are read with match.groups.name and inserted with $<name>.
  • Use the u flag and \p{L} for letters in any language.
  • Escape user input before building a pattern, and do not use the g flag with test.
  • Prefer URL, JSON.parse, DOM APIs, Intl and simple string methods when they fit the job, and avoid nested quantifiers.

Further reading