Regular expressions show up everywhere: form validation, log parsing, search-and-replace, URL routing, syntax highlighting. When AI generates code that processes text, there's a good chance it reaches for a regexWhat is regex?A compact pattern language for matching, searching, and replacing text, built into nearly every programming language and code editor.. The problem is that regex has a reputation for being unreadable, and AI-generated regex is often more complex than it needs to be.
You don't need to become a regex wizard. You need to read regex patterns when you encounter them, write simple ones, and know when a regex is overkill.
Creating a regexWhat is regex?A compact pattern language for matching, searching, and replacing text, built into nearly every programming language and code editor. in JavaScript
Two ways to create a regex. The literal syntax is the one you'll see 99% of the time:
// Literal — most commonconst pattern =/hello/;// Constructor — useful when pattern is dynamicconst dynamic =newRegExp('hello');const fromVar =newRegExp(userInput,'i');// pattern from a variable
Use the literal /pattern/ for hardcoded patterns. Use new RegExp() when the pattern comes from a variable — like a user's search query.
AI pitfall
AI sometimes uses new RegExp() for static patterns, adding unnecessary complexity. If the pattern is hardcoded, use the literal syntax. It's shorter and the engine can optimize it at parse time.
02
Flags
Flags go after the closing slash and change how the pattern behaves:
Flag
Name
What it does
g
Global
Find all matches, not just the first
i
Case-insensitive
/hello/i matches "Hello", "HELLO", "hElLo"
m
Multiline
^ and $ match start/end of each line, not just the string
s
DotAll
. matches newline characters too
u
Unicode
Proper Unicode support (emoji, accented chars)
'Hello hello HELLO'.match(/hello/gi);// ['Hello', 'hello', 'HELLO']'Hello hello'.match(/hello/);// ['hello'] — only first match, case-sensitive
03
Character classes — matching types of characters
Instead of matching exact characters, you can match categories:
Pattern
Matches
Opposite
\d
Any digit (0-9)
\D — anything except digits
\w
Word character (a-z, A-Z, 0-9, _)
\W — non-word characters
\s
Whitespace (space, tab, newline)
\S — non-whitespace
.
Any character except newline
—
[abc]
Any of a, b, or c
[^abc] — anything except a, b, c
[a-z]
Any lowercase letter
[^a-z] — not a lowercase letter
// Match a phone number like 06 12 34 56 78/\d{2}\s\d{2}\s\d{2}\s\d{2}\s\d{2}/.test('06 12 34 56 78');// true
04
Quantifiers — how many times
Quantifiers follow a character or group and say "match this N times":
Quantifier
Meaning
Example
+
1 or more
\d+ matches "1", "42", "99999"
*
0 or more
\d* matches "" or "123"
?
0 or 1 (optional)
https? matches "http" and "https"
{3}
Exactly 3
\d{3} matches "123" but not "12"
{2,4}
Between 2 and 4
\d{2,4} matches "12", "123", "1234"
{2,}
2 or more
\d{2,} matches "12" and beyond
// Match an email-like pattern (simplified)/\w+@\w+\.\w+/.test('alice@example.com');// true
Good to know
Quantifiers are greedy by default — they match as much as possible. Add ? after a quantifier to make it lazy (match as little as possible). This matters when parsing HTML or extracting quoted strings: /".*"/ grabs everything between the first and last quote, /".*?"/ grabs the shortest match.
05
Anchors — where to match
Anchors don't match characters. They match positions:
Anchor
Matches
^
Start of string (or line with m flag)
$
End of string (or line with m flag)
\b
Word boundary
/^hello$/.test('hello');// true — exact match/^hello$/.test('hello world');// false — has more text after/\bworld\b/.test('hello world');// true — "world" as a whole word/\bworld\b/.test('helloworld');// false — no word boundary
06
Groups and captures
Parentheses () create capture groups — they extract parts of a match:
const datePattern =/(\d{4})-(\d{2})-(\d{2})/;const match ='2025-03-15'.match(datePattern);
match[0];// '2025-03-15' — full match
match[1];// '2025' — first group (year)
match[2];// '03' — second group (month)
match[3];// '15' — third group (day)
Named groups (ES2018)
Numbered groups get confusing fast. Named groups make your intent clear:
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
lt;month>/
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
lt;day>/
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
lt;month>/
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
lt;day>/
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
lt;month>/
// Reformat date from YYYY-MM-DD to DD/MM/YYYY'2025-03-15'.replace(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,'__CODE_BLOCK_2__lt;day>/__CODE_BLOCK_2__lt;month>/__CODE_BLOCK_2__lt;year>');// '15/03/2025'
lt;year>');// '15/03/2025'
AI pitfall
AI often generates regex with numbered groups like CALLOUT, $2, $3. Named groups
AI pitfall
AI often generates regex with numbered groups like CALLOUT, $2, $3. Named groups __CALLOUT_3__lt;name> are almost always better — they survive refactoring and make the pattern self-documenting.
lt;name> are almost always better — they survive refactoring and make the pattern self-documenting.
07
The methods you'll actually use
JavaScript has several regexWhat is regex?A compact pattern language for matching, searching, and replacing text, built into nearly every programming language and code editor. Ask AI for more methods. These four cover virtually every use case:
Method
Returns
Use when
regex.test(str)
true / false
You just need yes/no
str.match(regex)
Array of matches or null
You need the matched text
str.replace(regex, replacement)
New string
You need to transform text
str.split(regex)
Array of parts
You need to break text apart
// Validation/^\d{5}$/.test('75001');// true — French postal code// Extraction'Call 06-12-34 or 07-56-78'.match(/\d{2}-\d{2}-\d{2}/g);// ['06-12-34', '07-56-78']// Replacement'Hello World'.replace(/\s+/g,' ');// 'Hello World'// Splitting'one, two, three'.split(/,\s*/);// ['one', 'two', 'three']
08
When NOT to use regexWhat is regex?A compact pattern language for matching, searching, and replacing text, built into nearly every programming language and code editor. Ask AI for more
This is just as important as knowing regex syntax. AI reaches for regex when simpler methods exist:
Task
Use this
Not this
Check if string contains text
str.includes('text')
/text/.test(str)
Check if string starts with
str.startsWith('http')
/^http/.test(str)
Check if string ends with
str.endsWith('.json')
/\.json$/.test(str)
Simple split
str.split(',')
str.split(/,/)
Simple replace
str.replaceAll('a', 'b')
str.replace(/a/g, 'b')
Use regex when the pattern involves variability — digits, optional parts, alternatives, repetition. If you're matching a fixed string, you don't need regex.