Skip to main content

Chapter 9 - Text Pattern Matching with Regular Expressions (JavaScript)

Here's a JavaScript-flavoured version of the same concepts, with small JS examples for each idea.


Manually finding patterns vs regex​

You can detect patterns (like US phone numbers) with plain string logic, but it's verbose and rigid, while regex does the same with a short pattern.

Example (manual phone check):

function isPhoneNumber(text) {
if (text.length !== 12) return false;
for (let i = 0; i < 3; i++) {
if (!/\d/.test(text[i])) return false;
}
if (text[3] !== "-") return false;
for (let i = 4; i < 7; i++) {
if (!/\d/.test(text[i])) return false;
}
if (text[7] !== "-") return false;
for (let i = 8; i < 12; i++) {
if (!/\d/.test(text[i])) return false;
}
return true;
}

Regex equivalent pattern: /\d{3}-\d{3}-\d{4}/.


Basic regex workflow​

Use a regex literal (/pattern/) or new RegExp(). Call .match() on a string or .test() on the regex.

Example:

const phoneRe = /\d{3}-\d{3}-\d{4}/;
const mo = "My number is 415-555-4242.".match(phoneRe);
console.log(mo[0]); // '415-555-4242'

Groups with parentheses​

Parentheses create capture groups so you can pull out parts of a match (index 1, 2, etc.).

Example:

const phoneRe = /(\d{3})-(\d{3}-\d{4})/;
const mo = "My number is 415-555-4242.".match(phoneRe);
console.log(mo[1]); // '415'
console.log(mo[2]); // '555-4242'

You can destructure groups into variables:

const [, area, rest] = mo;

Escaping special characters​

Characters like ()[]{}.+*?^$|\ have special meanings in regex; prefix with \ to match them literally.

Example (match (415) 555-4242):

const pattern = /(\(\d{3}\)) (\d{3}-\d{4})/;
const mo = "My phone number is (415) 555-4242.".match(pattern);
console.log(mo[1]); // '(415)'
console.log(mo[2]); // '555-4242'

Alternation with |​

| means "this or that", and you can combine it with groups for shared prefixes.

Example:

const petRe = /Cat(erpillar|astrophe|ch|egory)/;
const mo = "Catch me if you can.".match(petRe);
console.log(mo[0]); // 'Catch'
console.log(mo[1]); // 'ch'

.match() vs .matchAll() (search vs findall)​

  • .match(regex) without the g flag returns the first match (or null).
  • .match(regex) with the g flag returns all matches as a flat array (no groups).
  • .matchAll(regex) with g returns an iterator of full match objects (with groups).

Example (no groups, g flag):

const pattern = /\d{3}-\d{3}-\d{4}/g;
console.log("Cell: 415-555-9999 Work: 212-555-0000".match(pattern));
// ['415-555-9999', '212-555-0000']

With groups (use matchAll):

const pattern2 = /(\d{3})-(\d{3})-(\d{4})/g;
for (const mo of "Cell: 415-555-9999 Work: 212-555-0000".matchAll(pattern2)) {
console.log(mo[1], mo[2], mo[3]);
}
// '415' '555' '9999'
// '212' '555' '0000'

Character classes [...] and negated [^...]​

[...] matches any one character from the set; [^...] matches any character not in the set.

Example (vowels vs non-vowels):

const vowelRe = /[aeiouAEIOU]/g;
console.log("RoboCop eats BABY FOOD.".match(vowelRe));
// ['o', 'o', 'o', 'e', 'a', 'A', 'O', 'O']

const consonantRe = /[^aeiouAEIOU]/g;
console.log("RoboCop eats BABY FOOD.".match(consonantRe));
// includes consonants, spaces, punctuation

Shorthand character classes \d \w \s and opposites​

Same built-ins as Python:

  • \d digits, \D non-digits
  • \w letters/digits/underscore, \W non-\w
  • \s whitespace, \S non-whitespace

Example:

const pattern = /\d+\s\w+/g;
console.log("12 drummers, 11 pipers, 10 lords".match(pattern));
// ['12 drummers', '11 pipers', '10 lords']

Dot . wildcard​

. matches any character except newline.

Example:

const atRe = /.at/g;
console.log("The cat in the hat sat on the flat mat.".match(atRe));
// ['cat', 'hat', 'sat', 'lat', 'mat']

To match a literal dot, use \..


Quantifiers: ?, *, +, \{m,n\}​

Quantifiers say how many of the preceding piece to match.

  • ? – 0 or 1 (optional)
  • * – 0 or more
  • + – 1 or more
  • \{m\} – exactly m
  • \{m,n\} – between m and n (inclusive), \{m,\} / \{,n\} for open-ended

Examples:

const optEx = /42!?/;           // '42' or '42!'
const starEx = /Eggs(and spam)*/; // Eggs, Eggs and spam, ...
const plusEx = /(Ha)+/; // 'Ha', 'HaHa', ...
const countEx = /(Ha){3,5}/; // 3 to 5 'Ha'

Use parentheses if you want the quantifier to apply to a whole group, not just one char.


Greedy vs non-greedy (? after quantifier)​

*, +, \{m,n\} are greedy: they match as much as possible; adding ? makes them lazy (shortest possible).

Example:

const greedy = /(Ha){3,5}/;
console.log("HaHaHaHaHa".match(greedy)[0]); // 'HaHaHaHaHa'

const lazy = /(Ha){3,5}?/;
console.log("HaHaHaHaHa".match(lazy)[0]); // 'HaHaHa'

Similarly, .* is greedy, .*? is lazy.


.* and .*? (match "anything")​

.* means "any chars, 0+ times"; use in groups to capture "whatever is here".

Example (First/Last name):

const nameRe = /First Name: (.*) Last Name: (.*)/;
const mo = "First Name: Al Last Name: Sweigart".match(nameRe);
console.log(mo[1]); // 'Al'
console.log(mo[2]); // 'Sweigart'

Example of greedy vs lazy tags:

const lazyTag = /<.*?>/;
const greedyTag = /<.*>/;

Matching newlines with the s flag​

Normally . does not match \n. Add the s (dotAll) flag to let . match newlines too.

Example:

const noNl = /.*/;
console.log("Line1\nLine2".match(noNl)[0]); // 'Line1'

const withNl = /.*/s;
console.log("Line1\nLine2".match(withNl)[0]); // whole string

Anchors: ^, $, word boundaries \b / \B​

  • ^ – match at start of string
  • $ – match at end of string
  • ^...$ – whole string must match
  • \b – word boundary; \B – not a word boundary

Examples:

const beginsHello = /^Hello/;
const endsDigit = /\d$/;
const allDigits = /^\d+$/;

const wordRe = /\bcat.*?\b/g;
console.log("The cat found a catapult catalog in the catacombs.".match(wordRe));
// ['cat', 'catapult', 'catalog', 'catacombs']

const middleRe = /\Bcat\B/g;
console.log("certificate".match(middleRe)); // ['cat']

Case-insensitive matching with the i flag​

const regex = /hello/i;
console.log("HELLO World".match(regex)[0]); // HELLO

Substitution with replace() / replaceAll()​

Replace matches with a new string.

const result = "Agent Alice gave the documents to Agent Bob."
.replaceAll(/Agent \w+/g, "REDACTED");
console.log(result); // REDACTED gave the documents to REDACTED.

Verbose mode (no built-in, use new RegExp)​

JavaScript has no verbose flag, but you can build readable patterns by concatenating strings.

const phoneRegex = new RegExp(
"(" +
"\\d{3}" + // area code
"|\\(\\d{3}\\)" + // or area code in parens
")" +
"[\\s\\-.]?" + // separator
"\\d{3}" + // first 3 digits
"[\\s\\-.]" + // separator
"\\d{4}" // last 4 digits
);

Quick mental model​

  • Regex = mini-language for patterns over text (phone numbers, emails, etc.).
  • Build a pattern (literal /…/ or new RegExp()), then use .match() / .matchAll() / .test() and group/quantifier tools to extract exactly what you need.

Overall idea of the chapter​

JavaScript regex works almost identically to Python's re module. The main differences are syntax (/pattern/flags literals), flags as letters (g, i, s), and using .match() / .matchAll() / .replace() as string methods rather than re.compile() objects.