The Core Toolkit
Now that you know a regex describes a shape, you need a vocabulary for describing shapes more richly than "these exact letters." This phase is that vocabulary, and the good news is it's small. There are dozens of regex features in the world, but a handful do almost all the work.
We'll go piece by piece, each with a tiny example, then assemble them into something real at the end.
Literals - characters that mean themselves
You already met these in Phase 1. Most characters in a regex are literals: they match exactly
themselves. a matches an a, 7 matches a 7, - matches a dash.
pattern: dog
"good dog" ► MATCH (the literal run d-o-g)
"doge" ► MATCH (d-o-g appears, then more)
"cat" ► no match
What just happened: nothing fancy - each character stood for itself, in order. Literals are the floor everything else builds on; the special power comes from the few characters that don't mean themselves.
Character classes - "any one of these"
Often you don't want one exact character; you want "any digit" or "any letter." That's a character class - a description of a set of characters, any one of which matches a single position.
The everyday shortcuts:
\d any digit 0 1 2 3 4 5 6 7 8 9
\w any "word" character letters, digits, and underscore (a–z A–Z 0–9 _)
\s any whitespace space, tab, newline
📝 Terminology. A character class matches exactly one character - one position in the
text. \d doesn't mean "a number," it means "one digit." To match several, you'll add a quantifier
(next section).
pattern: \d
"room 7" ► MATCH (the 7)
"no number" ► no match (not a single digit present)
What just happened: \d matched the single character 7. It would also have matched the 3 in
B3 or the 0 in 2026 - any one digit, wherever it sits.
You can also build your own class with square brackets. [aeiou] means "any one of these
vowels." A range with a dash, like [a-f], means "any one character from a to f."
pattern: [aeiou]
"rhythm" ► no match (no a/e/i/o/u)
"cat" ► MATCH (the a)
What just happened: the brackets describe a custom set, and the engine matched the first character
that fell inside it - the a in cat. ⚠️ Gotcha - a dash inside brackets is a range. [a-z]
means "a through z," not "the letters a, dash, z." To match a literal dash, put it first or last:
[-az] or [az-]. This trips up everyone once.
Quantifiers - "how many"
A character class matches one position. Quantifiers say how many times the thing right before them may repeat. These four cover almost everything:
* zero or more (any amount, including none)
+ one or more (at least one)
? zero or one (optional)
{n} exactly n times
pattern: \d+
"room 7" ► MATCH on "7" (one or more digits)
"year 2026" ► MATCH on "2026" (grabs all four - "one or more" keeps going)
"no digits" ► no match
What just happened: \d+ means "one or more digits in a row." On year 2026 it didn't stop at
the first digit - it kept matching as long as the next character was also a digit, capturing the
whole 2026. That "keep going" behavior is important, and it's the seed of the greedy matching
trap we'll meet in Phase 3.
A quick tour of the others:
pattern: colou?r the u is optional (? = zero or one)
"color" ► MATCH (zero u's)
"colour" ► MATCH (one u)
"colouur" ► no match (two u's - ? allows at most one)
What just happened: ? made the u optional, so one pattern matched both the US and British
spellings - a tiny taste of why regex is worth it: one description, several real-world variations.
Anchors - "where in the text"
By default a pattern matches anywhere (Phase 1). Anchors pin a pattern to a position instead of matching a character:
^ the start of the line/text
$ the end of the line/text
Anchors match a position, not a character - think of them as "right here at the edge."
pattern: ^cat$ start, then c-a-t, then end - nothing else allowed
"cat" ► MATCH (the whole text is exactly "cat")
"category" ► no match (there's more after "cat", so it's not the end)
"the cat" ► no match (there's text before "cat", so it's not the start)
What just happened: wrapping cat in ^...$ turned "find cat anywhere" into "the text must be
exactly cat." This is the answer to the Phase 1 gotcha - anchors are how you demand a whole
match instead of a found-somewhere match. ⚠️ Gotcha - ^ means two different things. At the
start of a pattern, ^ is the start-anchor. But inside square brackets, [^abc] means "any
character except a, b, or c" - a negation. Same symbol, totally different job depending on where
it sits. Read carefully.
Groups - "treat this as one unit"
Parentheses group part of a pattern so a quantifier (or other operator) applies to the whole group, not one character. They also "capture" what matched, so a tool can pull it out later.
pattern: (ab)+ "ab" repeated one or more times
"ababab" ► MATCH (three ab's in a row)
"abc" ► MATCH on "ab" (one ab, then c stops it)
"ba" ► no match
What just happened: without the parentheses, ab+ would mean "an a, then one or more bs." The
group made + apply to the pair ab. Grouping describes repeated structures, not only repeated
single characters - and capturing is how search-and-replace knows which piece to keep.
Building something real: a simple date
Now assemble the toolkit. A date like 2026-06-19 has a clear shape: four digits, a dash, two
digits, a dash, two digits - and we want the whole string to be that and nothing else.
pattern: ^\d{4}-\d{2}-\d{2}$
"2026-06-19" ► MATCH
"26-6-19" ► no match (\d{4} needs four digits, \d{2} needs two)
"2026-06-19x" ► no match ($ demands the end right after the last digit)
What just happened: read it left to right like a sentence. ^ (start), \d{4} (exactly four
digits), - (a literal dash), \d{2} (exactly two digits), -, \d{2}, $ (end). That wall of
symbols from the guide's intro is now a plain English description you can read. That's the payoff of
the toolkit.
💡 Key point. This pattern checks the shape, not the meaning. It happily matches
9999-99-99, which is not a real date. Regex describes how text looks, not whether it's
sensible. Knowing where that line falls - and not asking regex to cross it - is half of using it
well.
A straight word: the "perfect email regex" is a trap
A rite of passage is trying to write one regex that matches every valid email address and rejects every invalid one. Don't. The rules for what's technically a valid email are genuinely strange and sprawling, and the "complete" regex people pass around is hundreds of characters long, unreadable, and still not fully correct.
What you actually want in real life is a good-enough shape check - "looks roughly like an email" - and then to confirm it's real by sending a message to it. A practical, readable pattern:
pattern: ^\S+@\S+\.\S+$
"[email protected]" ► MATCH
"ada@localhost" ► no match (no dot after the @)
"not an email" ► no match (no @)
What just happened: \S means "any non-whitespace character" (the uppercase counterpart of \s).
So this reads: start, some non-spaces, an @, some non-spaces, a literal dot (\. - escaped, because
a bare . is special; more in Phase 3), some non-spaces, end. It's not airtight, and it's not meant to
be - it catches obvious typos and stays readable, which is the right trade. Chasing perfection here is
how regex earns its scary reputation.
Recap
- Literals match themselves; they're the floor everything builds on.
- Character classes match one character from a set:
\d(digit),\w(word char),\s(whitespace), or your own[...]. - Quantifiers say how many:
*(zero or more),+(one or more),?(optional),{n}(exactly n). - Anchors pin to a position:
^(start),$(end) - this is how you demand a whole match. - Groups
(...)apply a quantifier to a whole unit and capture what matched. - Regex checks shape, not meaning - and "the perfect email regex" is a trap; aim for good-enough and readable.
← Phase 1: What a Regex Actually Is · Guide overview · Phase 3: Using Regex for Real →
Try it yourself
Edit the pattern or the sample text and watch the matches highlight live:
\b[\w.]+@[\w.]+\.\w+\b
Email [email protected] or [email protected] - but "hello world" matches nothing.
Practice
[
{
"type": "regex",
"task": "Write a regex that matches a date shaped like `2026-06-19` - four digits, a dash, two digits, a dash, two digits - and nothing else, not just a piece of a longer string.",
"mustMatch": ["2026-06-19", "1999-01-01", "0000-12-31"],
"mustNotMatch": ["26-6-19", "2026/06/19", "date: 2026-06-19", "2026-06-19 note"],
"hint": "Anchor it with ^ and $ so the whole string has to match, not just part of it."
}
]
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 3 questions
1. What does the character class `\d` match?
2. What does the quantifier `+` mean?
3. What do the anchors `^` and `$` let you demand?
Practice this 1 exercise
1. Write a regex that matches a date shaped like `2026-06-19` - four digits, a dash, two digits, a dash, two digits - and nothing else, not just a piece of a longer string.