ESC

Type to search the knowledge base.

Regular Expressions Essentials

Build and debug JS regex: literals vs constructor, flags, groups, lastIndex traps, and safe validation patterns for forms.

intermediate3 min read
  • javascript
  • regular-expressions

Regex is dense syntax that pays rent in validation, parsing, and search. Frontend interviews often ask you to match emails/URLs poorly — better: show you understand flags, groups, and stateful g/y methods.

Create a pattern

const re1 = /hello/i;              // literal — compile once
const re2 = new RegExp('hello', 'i'); // dynamic pattern from strings

const escaped = userInput.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re3 = new RegExp(`^${escaped}$`, 'i');

Never drop raw user input into new RegExp without escaping — ReDoS and broken patterns.

Flags you actually use

Flag Meaning
i ignore case
g global — find all; enables matchAll, advances lastIndex
m ^/$ per line
s . matches newline
u Unicode mode (code points, \p{...})
y sticky — match only at lastIndex
d indices for groups (modern engines)
const text = 'A a A';
text.match(/a/gi); // ['A', 'a', 'A']

Common methods

/re/.test(str);           // boolean
str.search(/re/);         // index or -1
str.match(/re/);          // first match + groups (no g)
str.match(/re/g);         // array of strings, no groups
str.matchAll(/re/g);      // iterator of full matches (needs g)
str.replace(/re/g, 'x');
str.replace(/re/g, (m, g1) => g1.toUpperCase());
str.split(/[,;]\s*/);

Groups

const re = /(\d{4})-(\d{2})-(\d{2})/;
const m = '2026-08-04'.match(re);
// m[0] full, m[1] year, m[2] month, m[3] day

const reN = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const n = reN.exec('2026-08-04');
n.groups.year; // '2026'

Non-capturing: (?:...). Lookahead: (?=...), (?!...). Lookbehind: (?<=...), (?<!...) — check support if you still target old Safari (lookbehind is modern).

lastIndex footgun

const re = /a/g;
re.test('a'); // true, lastIndex moved
re.test('a'); // false — starts after previous match on same string quirks
re.lastIndex = 0; // reset when reusing

Prefer str.matchAll or create a fresh regex per use for test loops. Don’t share a /g regex across async calls without resetting.

Validation without being evil

// pragmatic email check — not RFC complete
const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
EMAIL.test(value);

// Prefer HTML type=email + server validation for truth

Catastrophic backtracking example to avoid:

// BAD: (a+)+$ on long 'a's — can hang the tab
// Prefer possessive/atomic patterns or simple linear checks

If parsing HTML/XML, use a parser — not regex.

Interview answer (out loud)

“I use literals for static patterns and RegExp with escaped input for dynamic ones. I know g makes methods stateful via lastIndex, matchAll needs g, and named groups improve readability. For validation I keep patterns simple and still validate on the server; I avoid regex for HTML.”

Sticky flag for parsers

const tok = /\d+/y;
tok.lastIndex = 0;
tok.exec('12+34'); // '12'
tok.exec('12+34'); // null — next char isn't digit at lastIndex

Sticky (y) is useful for hand-written tokenizers: match only at the current position without scanning ahead.

Unicode property escapes

/^\p{Letter}+$/u.test('Женева'); // true
/\p{Emoji}/u.test('👍');

Requires u flag. Prefer these over brittle character ranges for international text when engine support matches your Baseline.

Further reading

Related guides