A regular expression is a pattern for matching text. That's the whole idea — everything else is just vocabulary for describing patterns more precisely. The syntax looks dense because it's compressed, not because the underlying concepts are complicated.
The characters that mean something special
Most characters in a pattern just match themselves — the letter "a" matches an "a". A handful mean something else:
.matches any single character except a newline.\dmatches any digit (0-9);\wmatches any letter, digit or underscore;\smatches any whitespace.*means "zero or more of the previous thing,"+means "one or more,"?means "zero or one" (i.e. optional).{3}means exactly 3 of the previous thing;{2,5}means between 2 and 5.[abc]matches any one of a, b or c;[^abc]matches anything except those.^anchors to the start of the string (or line, with the multiline flag);$anchors to the end.
Building one up from scratch
Say you want to match a simple phone number like 555-123-4567. Start from what you know: it's three digits, a dash, three digits, a dash, four digits.
Three digits is \d\d\d, or more compactly \d{3}. A literal dash is just -. Put it together:
^\d{3}-\d{3}-\d{4}$
The ^ and $ anchors mean the whole string has to match this shape, not just contain it somewhere — without them, this pattern would also match "call 555-123-4567 today", which may or may not be what you want.
Groups and why they matter
Wrapping part of a pattern in parentheses, like (\d{3})-(\d{3})-(\d{4}), creates a capture group — a way to pull out just that piece of the match rather than the whole thing. This is how you'd extract the area code separately from the rest of the number, instead of just confirming the whole string matches the shape.
Flags change how matching behaves
- Global (g) finds every match in the text instead of stopping at the first one.
- Case-insensitive (i) makes
catmatchCatandCATtoo. - Multiline (m) makes
^and$match the start/end of each line, not just the start/end of the whole string.
Where this actually gets used
Validating input formats (emails, phone numbers, postal codes), search-and-replace across a codebase, extracting structured data from unstructured log files, and configuring URL routing rules all come down to the same handful of building blocks above.
The fastest way to get comfortable is to test against real examples rather than writing a pattern blind. Our Regex Tester highlights every match live as you type the pattern, and lists out each captured group — which makes it much easier to see exactly where a pattern is matching too much, too little, or not at all.