Regular expressions look like line noise until the moment they save you an afternoon of fragile string-slicing code. The good news is that the syntax is small and mostly composable, so once you understand the handful of building blocks you can read and write almost any pattern. This guide builds regex up piece by piece using the JavaScript flavor, then assembles a few patterns you'd actually use.
What a regex actually is
A regular expression is a small program that describes a set of strings. You hand it some input text, and it answers questions: does this match? where? what did it capture? In JavaScript you write one with slashes:
jsconst re = /cat/; re.test("the cat sat"); // true re.test("dog"); // false
The pattern
/cat/catYou can also build one from a string with the
RegExpjsconst word = "cat"; const re = new RegExp(word); // same as /cat/
With the constructor you have to double-escape backslashes (
"\\d"\d/.../Literals and metacharacters
Most characters in a pattern match themselves. The exceptions are metacharacters, which have special meaning:
. ^ $ * + ? ( ) [ ] { } | \\.\+This is the single most common beginner bug. The pattern
/3.14/3.14.3x143 14/3\.14/Character classes
A character class, written with square brackets, matches exactly one character from a set:
js/[aeiou]/ // any single vowel /[0-9]/ // any single digit (range) /[a-fA-F]/ // a hex letter, either case
Ranges use a hyphen. You can combine ranges and individual characters in one class:
[a-z0-9_]A caret as the first character inside the brackets negates the class:
js/[^0-9]/ // any single character that is NOT a digit
Because some classes are so common, regex provides shorthands:
- — a digit, same astext
\dtext[0-9] - — a "word" character:text
\wtext[A-Za-z0-9_] - — whitespace (space, tab, newline, and more)text
\s - ,text
\D,text\W— the negated versions of eachtext\S
And
.sA subtle point: inside a character class, most metacharacters lose their power.
[.+*]]\^-Quantifiers
Quantifiers say how many of the preceding element to match:
- — zero or moretext
* - — one or moretext
+ - — zero or one (i.e. optional)text
? - — exactly ntext
{n} - — n or moretext
{n,} - — between n and m, inclusivetext
{n,m}
Examples:
js/colou?r/ // matches "color" and "colour" /\d{3}-\d{4}/ // 123-4567 /a{2,4}/ // "aa", "aaa", or "aaaa" /\w+/ // one or more word characters
Quantifiers attach to whatever immediately precedes them: a single character, a character class, or a group.
Greedy vs. lazy
By default quantifiers are greedy — they grab as much as they can while still allowing the overall pattern to match. This trips people up constantly. Consider extracting the contents of an HTML tag:
js"<b>one</b><b>two</b>".match(/<b>(.*)<\/b>/)[1]; // "one</b><b>two"
The
.*</b>?js"<b>one</b><b>two</b>".match(/<b>(.*?)<\/b>/)[1]; // "one"
(For real HTML, use a DOM parser — but lazy quantifiers are exactly the right tool for many small text-extraction jobs.)
Anchors and boundaries
Anchors don't match characters; they match positions.
- — start of the string (or start of a line, with thetext
^flag)textm - — end of the string (or end of a line, withtext
$)textm - — a word boundary, the edge between atext
\band a non-text\wcharactertext\w
Anchors are how you require a pattern to span the whole input rather than just appearing within it:
js/^\d+$/.test("42"); // true — the whole string is digits /^\d+$/.test("42px"); // false
Word boundaries let you match whole words.
/\bcat\b/catjs/\bcat\b/.test("category"); // false /\bcat\b/.test("the cat"); // true
Groups and capturing
Parentheses do two jobs at once: they group a sub-pattern so a quantifier can apply to the whole thing, and they capture the matched text for later use.
js/(ab)+/ // one or more repetitions of "ab"
When a regex with capturing groups matches, you get the captured substrings back:
jsconst m = "2026-06-02".match(/(\d{4})-(\d{2})-(\d{2})/); m[1]; // "2026" (first group) m[2]; // "06" m[3]; // "02"
Named groups
Numbered groups get unreadable fast. Name them with
(?<name>...)groupsjsconst m = "2026-06-02".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/); m.groups.year; // "2026" m.groups.month; // "06"
Non-capturing groups
If you only need grouping for a quantifier and don't care about capturing, use
(?:...)js/(?:https?:\/\/)?example\.com/ // the protocol is optional, but not captured
Alternation
The pipe
|js/^cat|dog$/ // ^cat OR dog$ — probably not what you meant /^(cat|dog)$/ // exactly "cat" or exactly "dog"
Almost always you want alternation wrapped in a group so the surrounding anchors and quantifiers apply to both branches.
Flags
Flags go after the closing slash and change how the whole pattern behaves:
- — global: find all matches, not just the firsttext
g - — case-insensitivetext
i - — multiline:text
mandtext^match at line breaks, not just string endstext$ - — dotall:text
salso matches newlinestext. - — unicode: correct handling of code points beyond the basic rangetext
u
The
gmatchAlljsconst text = "a1 b2 c3"; for (const m of text.matchAll(/([a-z])(\d)/g)) { console.log(m[1], m[2]); // a 1, then b 2, then c 3 }
One gotcha worth knowing: a
RegExpgylastIndex.test().exec()gPutting it together
With the pieces in hand, here are a few patterns built from what we've covered.
A hex color — a
#js/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
A simple time in 24-hour form — hours
00230059js/^([01]\d|2[0-3]):[0-5]\d$/
Pulling key-value pairs from a query string fragment:
jsconst q = "name=ada&lang=js&year=2026"; const pairs = [...q.matchAll(/(?<key>\w+)=(?<val>\w+)/g)] .map(m => [m.groups.key, m.groups.val]); // [["name","ada"],["lang","js"],["year","2026"]]
A word of honesty about email: the truly correct email regex is enormous and still rejects valid addresses. For real validation, check for a single
@js/^[^\s@]+@[^\s@]+\.[^\s@]+$/
Habits that keep regex maintainable
Regex rewards a few disciplines. Anchor with
^$Finally, never ship a pattern you haven't run against real inputs, including the messy edge cases and the strings that should not match. Building the pattern incrementally and testing as you go beats staring at a wall of metacharacters. You can prototype against your own sample text in the free Cosmovex regex tester, which highlights matches and capture groups live so you can see exactly what each part of the pattern is doing before it reaches production. Start with the literal you know matches, add one construct at a time, and confirm each step does what you expect.