Skip to content
elephantoo

Regular expressions

Lesson 22 of 34 18 min read

Patterns, flags, character classes, groups, named captures and replace/matchAll.


A regular expression (regex) is a pattern that describes text: "a word starting with a capital letter", "six digits", "anything that looks like an email". They're a compact mini-language built into JavaScript for searching, validating, extracting and replacing text. They look cryptic at first, but a small set of building blocks covers most real-world needs.

Creating a regex#

JavaScript
const literal = /hello/i;                  // literal syntax – most common
const built = new RegExp("hello", "i");    // constructor – for patterns built at runtime

console.log(literal.test("Hello, world")); // true
console.log(built.test("say HELLO"));      // true

Use the constructor when the pattern comes from a variable. If that variable holds user input, escape it first so characters like . or ? are treated literally — modern engines have RegExp.escape(str) for this (Node 24+, current browsers).

The core methods#

JavaScript
const text = "Order #123 shipped, order #456 pending";

console.log(/order/i.test(text));      // true – does it match?
console.log(text.search(/#\d+/));      // 6 – index of first match
console.log(text.match(/#\d+/)[0]);    // "#123" – first match
console.log(text.match(/#\d+/g));      // [ '#123', '#456' ] – all matches (g flag)
console.log(text.replace(/#\d+/g, "#***")); // Order #*** shipped, order #*** pending
console.log("a, b;c  d".split(/[,;\s]+/));  // [ 'a', 'b', 'c', 'd' ]
MethodReturns
regex.test(str)true/false
str.match(regex)first match details, or all matches with g (or null)
str.matchAll(regex)an iterator of every match with groups (needs g)
str.replace(regex, x) / replaceAlla new string
str.split(regex)an array
str.search(regex)index of first match or -1

Building blocks#

Characters and classes

PatternMatches
abcthe exact text "abc"
.any character except newline
\d / \Da digit / a non-digit
\w / \Wa word character [A-Za-z0-9_] / anything else
\s / \Swhitespace (space, tab, newline) / non-whitespace
[aeiou]any one of these characters
[a-z0-9]a range
[^0-9]any character not in the set
\. \? \(a literal ., ?, ( — special characters need a backslash

Quantifiers: how many?

PatternMeaning
a?0 or 1
a*0 or more
a+1 or more
a{3}exactly 3
a{2,4}2 to 4
a{2,}2 or more
JavaScript
console.log(/colou?r/.test("color"), /colou?r/.test("colour")); // true true
console.log("aaa bb c".match(/\w+/g));   // [ 'aaa', 'bb', 'c' ]
console.log(/^\d{6}$/.test("411001"));   // true – an Indian PIN code
console.log(/^\d{6}$/.test("41100"));    // false

Anchors and boundaries

PatternMeaning
^start of the string (or line, with the m flag)
$end of the string (or line, with m)
\ba word boundary
JavaScript
console.log(/cat/.test("concatenate"));    // true – "cat" appears inside
console.log(/\bcat\b/.test("concatenate")); // false – not a whole word
console.log(/\bcat\b/.test("the cat sat")); // true

For validation, always anchor with ^...$. Without them, /\d{6}/ happily matches "abc1234567xyz".

Alternation

JavaScript
const imageFile = /\.(png|jpe?g|gif|webp)$/i;
console.log(imageFile.test("photo.JPG"));  // true
console.log(imageFile.test("notes.txt"));  // false

Flags#

FlagNameEffect
gglobalfind all matches
iignore casea matches A
mmultiline^/$ match at each line
sdotAll. also matches newlines
uunicodecorrect handling of emoji and \p{...} classes
vunicodeSetsan upgraded u with set operations (ES2024)
dindicesreport start/end positions of groups
ystickymatch only at lastIndex
JavaScript
const poem = "Roses are red\nViolets are blue";
console.log(poem.match(/^\w+/gm)); // [ 'Roses', 'Violets' ]

console.log("Ünïcödé".match(/\p{L}/gu).length); // 7 – letters in any language

Groups and captures#

Parentheses group parts of a pattern and capture what they matched:

JavaScript
const date = "Published 2026-09-30";
const m = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(m[0]); // "2026-09-30" – the whole match
console.log(m[1], m[2], m[3]); // 2026 09 30
console.log(m.index); // 10

Named groups

Names make complex patterns readable:

JavaScript
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { year, month, day } = "Due 2026-12-25".match(re).groups;
console.log(`${day}/${month}/${year}`); // 25/12/2026

Non-capturing groups

(?:...) groups without capturing — useful with quantifiers or alternation:

JavaScript
console.log("hahaha".match(/(?:ha)+/)[0]); // "hahaha"

Replacing with groups

JavaScript
console.log("2026-09-30".replace(/(\d+)-(\d+)-(\d+)/, "$3/$2/$1")); // 30/09/2026
console.log("Ada Lovelace".replace(/(?<first>\w+) (?<last>\w+)/, "$<last>, $<first>")); // Lovelace, Ada

// A function as the replacement – called for each match
const prices = "Tea ₹20, Coffee ₹45";
console.log(prices.replace(/₹(\d+)/g, (_, amount) => `₹${Number(amount) * 2}`)); // Tea ₹40, Coffee ₹90

Iterating all matches with matchAll#

JavaScript
const log = "user=ada id=7; user=alan id=12";
for (const match of log.matchAll(/user=(?<name>\w+) id=(?<id>\d+)/g)) {
  console.log(match.groups.name, Number(match.groups.id));
}
Output
ada 7
alan 12

matchAll requires the g flag and gives you full details (groups, index) for every match.

Greedy vs lazy#

Quantifiers are greedy: they match as much as possible. Add ? to make them lazy:

JavaScript
const html = "<b>bold</b> and <i>italic</i>";
console.log(html.match(/<.+>/)[0]);  // "<b>bold</b> and <i>italic</i>" – greedy!
console.log(html.match(/<.+?>/g));   // [ '<b>', '</b>', '<i>', '</i>' ] – lazy

Don't parse real HTML with regex — use the DOM (DOMParser) instead. Small, predictable snippets like this are fine.

Lookahead and lookbehind#

Check what comes before or after without including it in the match:

JavaScript
console.log("Price: 100 USD, 200 INR".match(/\d+(?= INR)/)[0]);   // "200" – followed by " INR"
console.log("Total: ₹499".match(/(?<=₹)\d+/)[0]);                // "499" – preceded by ₹

// Password must contain a digit and an uppercase letter, 8+ characters
const strong = /^(?=.*\d)(?=.*[A-Z]).{8,}$/;
console.log(strong.test("elephant"), strong.test("Elephant9")); // false true

Practical patterns#

JavaScript
const patterns = {
  pin: /^\d{6}$/,                                 // Indian PIN code
  hexColour: /^#(?:[0-9a-f]{3}){1,2}$/i,          // #fff or #A16207
  slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,             // url-friendly-slug
  simpleEmail: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,      // good-enough email check
  time24: /^([01]\d|2[0-3]):[0-5]\d$/,            // 00:00 – 23:59
};

console.log(patterns.hexColour.test("#A16207")); // true
console.log(patterns.slug.test("arrays-and-objects")); // true
console.log(patterns.slug.test("Bad Slug!"));    // false
console.log(patterns.time24.test("23:59"), patterns.time24.test("24:00")); // true false

A robust slugify

JavaScript
function slugify(text) {
  return text
    .normalize("NFKD")                 // split accented letters: é → e + ´
    .replace(/[\u0300-\u036f]/g, "")   // remove the accent marks
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")       // anything else → hyphen
    .replace(/^-+|-+$/g, "");          // trim hyphens at the ends
}

console.log(slugify("  Café & Crème Brûlée!  ")); // cafe-creme-brulee
console.log(slugify("Arrays & Objects"));          // arrays-objects

The lastIndex trap#

A regex with g or y remembers where it stopped in lastIndex, so reusing it with test can give alternating results:

JavaScript
const re = /a/g;
console.log(re.test("a"), re.test("a"), re.test("a")); // true false true 😕

Don't use the g flag with test(), or create the regex fresh each time.

Tips#

  • Build and debug patterns on a site like regex101.com (choose the JavaScript flavour) — it explains each part.
  • Prefer readable code over clever regex. Two simple checks beat one unreadable pattern.
  • Email and URL validation via regex is always approximate. For URLs, URL.canParse(str) or new URL(str) is better; for email, use type="email" plus a confirmation email.
  • Beware of patterns with nested quantifiers like (a+)+ on user input — they can take exponential time ("ReDoS").

Common mistakes#

  • Forgetting to escape . — /1.5/ matches "105".
  • Forgetting anchors in validation patterns.
  • Expecting replace with a string pattern or a regex without g to replace everything.
  • Using match with g and expecting groups — use matchAll.

What's next#

Next, handle dates and times correctly and format numbers, currencies and dates for any locale with Intl.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What does the g flag do in /cat/g?

  2. 2.Which string does /^\d{3}-\d{4}$/ match?

  3. 3.In "2026-09-30".replace(/(\d+)-(\d+)-(\d+)/, "$3/$2/$1"), what is the result?

Finished reading?

Mark this lesson complete to track your progress.