A regular expression looks like noise until you learn it piece by piece — each symbol does exactly one small job, and patterns are just those small jobs combined.
/cat/ matches the literal text "cat"
/[aeiou]/ matches any single vowel
/[^0-9]/ matches any character that is NOT a digit
/[a-z]/ matches any lowercase letter
Square brackets define a set of characters to match at that one position — a caret ^ inside brackets negates the set.
\d any digit (same as [0-9])
\w any word character (letters, digits, underscore)
\s any whitespace
. any character at all (except newline, by default)
a+ one or more "a"
a* zero or more "a"
a? zero or one "a"
a{2,4} between 2 and 4 "a"s
A phone number pattern makes this concrete: /^\d{3}-\d{3}-\d{4}$/ matches exactly "123-456-7890" — three digits, a dash, three digits, a dash, four digits, start to end.
const match = '2026-09-30'.match(/(\d{4})-(\d{2})-(\d{2})/)
// match[1] = '2026', match[2] = '09', match[3] = '30'
Parentheses create a capture group — beyond just matching, they let you pull specific pieces out of the matched text by index.
const emailPattern = /^[\w.-]+@[\w-]+\.[a-zA-Z]{2,}$/
emailPattern.test('user@example.com') // true
emailPattern.test('not-an-email') // false
Reading it piece by piece: one or more word characters, dots, or dashes; an @; a domain name; a dot; a top-level domain of at least two letters. No single email regex is fully RFC-compliant, but this covers the overwhelming majority of real addresses.
Comments