CodeMIRAI
0 XP

Regular expressions

6 min read 13 runnable examples Quiz included

A regular expression (regex, or regexp) is a pattern that describes text: "six digits", "a word starting with a capital letter", "anything that looks like an email address". With one you can check whether text matches, find every match, extract parts, replace matches and split text. Regexes look cryptic at first, but they are built from a small set of pieces, and this lesson goes through them one at a time.

Writing a regex

A regex is written between slashes, optionally followed by flags:

JavaScript
const pattern = /cat/;            // a regex literal: matches the letters c, a, t
console.log(pattern.test("concatenate"));    // true: "cat" appears inside
console.log(pattern.test("Cat"));            // false: case matters
console.log(/cat/i.test("Cat"));             // the i flag ignores case

const word = "dog";
const built = new RegExp(word, "i");         // build one from a string at run time
console.log(built.test("Hot DOG"), built.source, built.flags);

The methods that use regexes

MethodDoesReturns
regex.test(text)is there a match?true / false
text.match(regex)the first match (or all, with g)an array, or null
text.matchAll(regex)every match, with details (needs g)an iterator
text.search(regex)where the first match startsan index, or -1
text.replace(regex, x)replaces the first match (all, with g)a new string
text.split(regex)splits wherever it matchesan array
JavaScript
const text = "Order 12 shipped, order 7 pending, order 305 delivered";
console.log(/\d+/.test(text));
const first = text.match(/\d+/);         // an array holding the first match...
console.log(first[0], "at index", first.index);   // ...and .index, where it starts
console.log(text.match(/\d+/g));         // every match
console.log(text.search(/pending/));
console.log(text.replace(/order/gi, "#"));
console.log("a, b;c  d".split(/[,;\s]+/));

Character classes: which characters

PatternMatches
.any one character except a new line
\d / \Da digit 0 to 9 / anything but a digit
\w / \Wa "word" character (letter, digit, _) / anything else
\s / \Swhitespace (space, tab, new line) / anything else
[abc]one of a, b or c
[a-z], [A-Z0-9]one character in a range
[^abc]one character that is not a, b or c
JavaScript
console.log("R2-D2 and C-3PO".match(/\d/g));
console.log("hello world".match(/[aeiou]/g).length, "vowels");
console.log("Room 101, floor 3".replace(/\D/g, ""));      // keep only the digits
console.log("tab\tand space".split(/\s/));

Quantifiers: how many

PatternMeans
x*zero or more x
x+one or more x
x?zero or one x (optional)
x{3}exactly 3
x{2,}2 or more
x{2,4}from 2 to 4
JavaScript
console.log(/colou?r/.test("color"), /colou?r/.test("colour"));   // the u is optional
console.log("aaa bb c".match(/b+/)[0]);
console.log(/^\d{6}$/.test("411001"), /^\d{6}$/.test("41100"));   // exactly 6 digits
console.log("haaaappy".replace(/a{2,}/, "a"));

Greedy and lazy

Quantifiers are greedy: they match as much as they can. Adding ? after one makes it lazy: as little as possible:

JavaScript
const html = "<b>bold</b> and <i>italic</i>";
console.log(html.match(/<.+>/)[0]);     // greedy: from the first < to the LAST >
console.log(html.match(/<.+?>/)[0]);    // lazy: stops at the first >
console.log(html.match(/<.+?>/g));

Anchors: where

PatternMeans
^the start of the text (or of a line, with m)
$the end of the text (or of a line, with m)
\ba word boundary: between a word character and anything else

Validation patterns almost always use ^...$, so the whole text must match, not just part of it:

JavaScript
console.log(/\d{4}/.test("abc12345xyz"));        // true: contains 4 digits somewhere
console.log(/^\d{4}$/.test("abc12345xyz"));      // false: must be exactly 4 digits
console.log("cat catalog scatter".match(/\bcat\b/g));   // only the whole word

Groups and alternation

Round brackets ( ) group part of a pattern, so a quantifier can apply to the whole group, and they capture what that part matched. | means "or":

JavaScript
console.log(/^(ha)+!$/.test("hahaha!"));
console.log(/^(cat|dog)s?$/.test("dogs"), /^(cat|dog)s?$/.test("cows"));

const date = "2026-10-31";
const m = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
console.log(m[0], "| year", m[1], "| month", m[2], "| day", m[3]);

const named = date.match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/);
console.log(named.groups.day + "/" + named.groups.month + "/" + named.groups.year);

m[0] is the whole match and m[1], m[2]... are the groups in order. Named groups (?<name>...) are easier to read: match.groups.name. A group that should group but not capture is written (?:...).

Groups in replacements

In replace, $1, $2 (or $<name>) insert what a group captured. Or pass a function, which receives the match and the groups and returns the replacement:

JavaScript
console.log("2026-10-31".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"));
console.log("Asha Rao".replace(/(?<first>\w+) (?<last>\w+)/, "$<last>, $<first>"));

const title = "the quick brown fox".replace(/\b\w/g, (letter) => letter.toUpperCase());
console.log(title);

const prices = "pen 10, pad 45, bag 900";
console.log(prices.replace(/\d+/g, (n) => Number(n) * 2));   // double every number

matchAll: every match with its details

JavaScript
const tweet = "Learning #javascript and #regex on #CodeMIRAI";
for (const m of tweet.matchAll(/#(\w+)/g)) {
  console.log(`tag "${m[1]}" at position ${m.index}`);
}
console.log([...tweet.matchAll(/#(\w+)/g)].map((m) => m[1].toLowerCase()));

Flags

FlagEffect
gglobal: find all matches, not just the first
iignore case
mmultiline: ^ and $ match at every line
s"dotAll": . matches new lines too
uUnicode: handle emoji and other characters correctly
JavaScript
const lines = "apple\nbanana\navocado";
console.log(lines.match(/^a\w+/g));       // only the start of the whole text
console.log(lines.match(/^a\w+/gm));      // the start of every line

Escaping special characters

These characters have special meanings: . * + ? ^ $ { } ( ) [ ] | \ /. To match one literally, put a backslash before it:

JavaScript
console.log(/3.14/.test("3x14"));        // true! the dot matches any character
console.log(/3\.14/.test("3x14"));       // false: \. is a real dot
console.log("1+1=2".replace(/\+/g, " plus "));
console.log(/^\$\d+$/.test("$100"));

In new RegExp("..."), the pattern is a string, so each backslash must be doubled: new RegExp("\\d+").

Lookaheads: rules without consuming

(?=...) checks that something follows, without including it in the match; (?!...) checks that it doesn't. Several lookaheads at the start of a pattern can check several rules at once, which is how password rules are often written:

JavaScript
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;   // lower, upper, digit, 8+ characters
for (const p of ["password", "Password1", "Pass1", "PASSWORD99", "learnJS2026"]) {
  console.log(p.padEnd(12), strong.test(p));
}
console.log("price: 100USD, 250INR".match(/\d+(?=INR)/)[0]);   // the number before INR

Practical patterns

JavaScript
const patterns = {
  pinCode: /^[1-9]\d{5}$/,                        // Indian PIN: 6 digits, not starting with 0
  mobile: /^[6-9]\d{9}$/,                         // Indian mobile: 10 digits starting 6-9
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,            // simple: something@something.something
  hexColour: /^#([0-9a-f]{3}|[0-9a-f]{6})$/i,
  time24: /^([01]\d|2[0-3]):[0-5]\d$/,
};
const tests = { pinCode: ["411001", "011001"], mobile: ["9876543210", "1234567890"], email: ["asha@example.com", "asha@example"],
  hexColour: ["#0aa9cf", "#xyz"], time24: ["23:59", "24:00"] };
for (const [name, values] of Object.entries(tests)) {
  console.log(name.padEnd(10), values.map((v) => `${v} ${patterns[name].test(v) ? "ok" : "no"}`).join(" | "));
}

// tidy messy spacing
console.log("  too    many   spaces  ".trim().replace(/\s+/g, " "));

When not to use a regex

Regexes are great for patterns in text, but poor for structured formats with nesting, like HTML or JSON: use DOMParser and JSON.parse instead. And for a plain search, includes, startsWith or split(",") are clearer. A regex nobody can read is a bug waiting to happen: keep them short, add a comment saying what they match, and test them on examples (sites like regex101.com explain a pattern piece by piece).

Key points

  • Write regexes as /pattern/flags (or new RegExp(string, flags)).
  • test checks, match/matchAll find, replace changes, split cuts.
  • \d \w \s . and [...] say which characters; * + ? {n,m} say how many; ^ $ \b say where.
  • ( ) groups and captures ($1, named (?<n>...)), | means or, \ escapes special characters.
  • g finds all, i ignores case, m makes ^/$ work per line.
  • Anchor validation patterns with ^...$.

Practice

  1. Check whether a string is exactly 4 digits (a year like 2026).
  2. Find every word that starts with a capital letter in "Asha met Leo in Pune on Monday".
  3. Turn "31/10/2026" into "2026-10-31" with one replace.
  4. Count the sentences in a paragraph by splitting on ., ! or ?.
  5. Write a pattern for a username: 3 to 16 letters, digits or underscores, starting with a letter.
Finished this lesson?Mark it complete to earn +20 XP and move your progress on.