Blog

Blog · Complete Guides

The Complete Guide to Regular Expressions in 2026

A complete guide to regular expressions (regex) in JavaScript — character classes, anchors, quantifiers, groups, alternation, flags, lookaround and the string methods — with practical examples throughout.

XCODX Team · 6 min read

A regular expression (regex) is a pattern for matching text — validating input, searching, extracting and replacing. They look cryptic but are built from a small set of pieces. This complete guide teaches regex in JavaScript from character classes to lookaround, with practical examples you can run, plus the pitfalls to avoid.

Creating a regex

JavaScript has two ways to make a regex: a literal between slashes (most common), or the RegExp constructor (when you build the pattern from a string). Flags go after the closing slash or as a second argument.

const re1 = /hello/i;                 // literal, case-insensitive
const re2 = new RegExp("hello", "i");  // constructor (dynamic patterns)

re1.test("Hello world");   // true

Character classes

Character classes match a *type* of character. The shorthands cover the common cases; square brackets define your own set.

  • \d — a digit (0–9); \D — a non-digit.
  • \w — a word character (letter, digit, underscore); \W — the opposite.
  • \s — whitespace; \S — non-whitespace.
  • . — any character except a newline.
  • [abc] — any one of a, b, c; [a-z] — a range; [^abc] — anything except a, b, c.
/\d\d\d/.test("123");        // true — three digits
/[a-z]+/i.test("Hello");      // true — one or more letters
/[^0-9]/.test("a");           // true — a non-digit

Anchors and boundaries

Anchors match a *position*, not a character. ^ and $ tie the pattern to the start and end; \b matches a word boundary — the edge between a word character and a non-word character.

/^hello/.test("hello world");   // true — starts with "hello"
/world$/.test("hello world");   // true — ends with "world"
/\bcat\b/.test("the cat sat");   // true — whole word "cat"
/\bcat\b/.test("category");      // false — "cat" is part of a word

Quantifiers

Quantifiers say *how many* times the preceding item may repeat. By default they are greedy (match as much as possible); add ? to make them lazy (as little as possible).

  • * — zero or more; + — one or more; ? — zero or one (optional).
  • {3} — exactly 3; {2,4} — 2 to 4; {2,} — 2 or more.
  • Add ? after any quantifier for lazy matching: +?, *?, {2,4}?.
/\d+/.exec("abc123")[0];        // "123" — one or more digits
/colou?r/.test("color");         // true — the "u" is optional
/".*"/.exec('"a" "b"')[0];       // '"a" "b"' — greedy
/".*?"/.exec('"a" "b"')[0];      // '"a"'    — lazy

Groups and capturing

Parentheses group part of a pattern and capture what it matched, so you can extract or reuse it. Use (?:…) for a non-capturing group, and (?<name>…) for a named capture that’s easier to read.

const m = /(\d{4})-(\d{2})-(\d{2})/.exec("2026-07-29");
m[1]; // "2026"  m[2]; // "07"  m[3]; // "29"

// named groups
const d = /(?<year>\d{4})-(?<month>\d{2})/.exec("2026-07");
d.groups.year;  // "2026"

Alternation

The pipe | means "or" — match one alternative or another. Group it with parentheses to bound the choice.

/cat|dog/.test("I have a dog");   // true
/^(yes|no)$/.test("yes");         // true — exactly "yes" or "no"

Flags

Flags change how the whole regex behaves. Combine them (e.g. /…/gi).

  • g — global: find all matches, not just the first.
  • i — case-insensitive.
  • m — multiline: ^ and $ match at line breaks.
  • s — dotall: . also matches newlines.
  • u — unicode mode (needed for \p{…} and correct surrogate handling).
  • y — sticky: match only at the exact lastIndex position.
"a1 b2 c3".match(/\w\d/g);   // ["a1", "b2", "c3"] — global

Lookahead and lookbehind

Lookaround asserts what comes before or after a position without consuming it. Useful for "match X only if followed/preceded by Y" — like validating a password has a digit, without including the digit in the match.

/\d+(?= dollars)/.exec("50 dollars")[0];   // "50"  — positive lookahead
/(?<=\$)\d+/.exec("$50")[0];                // "50"  — positive lookbehind
/foo(?!bar)/.test("foobaz");                // true  — negative lookahead

Using regex in JavaScript

Regex plugs into several string and RegExp methods:

  • regex.test(str) — does it match? Returns a boolean.
  • str.match(regex) — the first match, or all matches with the g flag.
  • str.matchAll(regex) — an iterator of all matches with capture groups.
  • str.replace(regex, replacement) — replace matches; use $1 or $<name> for groups.
  • str.split(regex) — split on a pattern.
"2026-07-29".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"); // "29/07/2026"
for (const m of "a1 b2".matchAll(/(\w)(\d)/g)) console.log(m[1], m[2]);

Pitfalls and best practices

  • Don’t over-validate with regex. Email/URL validation is notoriously hard to get right — a loose check plus real verification beats a monstrous pattern. For email, a simple /^\S+@\S+\.\S+$/ plus sending a confirmation is more reliable than a "perfect" regex.
  • Watch catastrophic backtracking. Nested quantifiers on overlapping patterns (like (a+)+) can hang on certain inputs (a ReDoS risk). Keep patterns simple and avoid ambiguous nesting.
  • Escape special characters when matching literals: ., *, +, ?, (, ), [, ], {, }, |, \, ^, $ all have meaning. Use RegExp.escape() (ES2025) or a backslash.
  • Comment complex patterns — a regex you can’t read is a bug waiting to happen. Break it up or add a comment explaining intent.

Regex cheat sheet

  • \d \w \s — digit, word char, whitespace (uppercase = negation).
  • ^ $ \b — start, end, word boundary.
  • * + ? and {n,m} — quantifiers (add ? for lazy).
  • ( ) capture, (?: ) non-capturing, (?<name> ) named.
  • | alternation; [ ] character set; . any char.
  • Flags: g global, i ignore-case, m multiline, s dotall, u unicode.
  • (?= ) (?! ) (?<= ) (?<! ) — look-ahead/behind (positive/negative).

Frequently asked questions

What is a regular expression?
A regular expression (regex) is a pattern used to match, search, extract or replace text. It’s made of literal characters plus special symbols — character classes like \d for digits, quantifiers like + for "one or more", anchors like ^ for the start of a string, and groups. Regex is supported in virtually every programming language and in tools like editors and command-line utilities.
What is the difference between greedy and lazy matching?
By default quantifiers are greedy — they match as much text as possible while still allowing the overall pattern to succeed. Adding a ? after a quantifier makes it lazy, matching as little as possible. For example, against "a" "b", the pattern ".*" (greedy) matches the whole "a" "b", while ".*?" (lazy) matches just "a". Lazy matching is useful when you want the shortest match between delimiters.
How do I validate an email with regex?
You generally shouldn’t try to fully validate email with regex — the real specification is extremely complex, and strict patterns reject valid addresses. Use a simple sanity check like /^\S+@\S+\.\S+$/ to catch obvious mistakes, then verify the address for real by sending a confirmation email. That combination is far more reliable than any single "perfect" email regex.
What does the g flag do in regex?
The g (global) flag makes the regex find all matches in a string rather than stopping at the first. With String.match, adding g returns an array of every match; without it you get just the first match plus capture groups. The g flag is also required by matchAll and by replace/replaceAll when you want to replace every occurrence, not just the first.

Test regular expressions live in XCODX Studio — run JavaScript in the browser and iterate on patterns instantly. See also the complete guide to JavaScript async and JavaScript best practices.