Regular Expressions Essentials
Build and debug JS regex: literals vs constructor, flags, groups, lastIndex traps, and safe validation patterns for forms.
- 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
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.