Character Classes & Quantifiers
The dot from Lesson 2 matches any character — sometimes too broad. What if you want "any digit," or "any letter, but not a digit"? That's what character classes are for. And once you can describe "one character from this set," you'll immediately want to say how many times that character (or group) should repeat — that's what quantifiers do.
💡 The Bottom Line
Character classes — [abc], [a-z], \d, \w,
\s — describe "match one character from this set." Quantifiers — *,
+, ?, {n,m} — describe "how many times in a row." Combine
the two and you can describe realistic, variable-length patterns like phone numbers or dates.
Custom character classes
Square brackets define a set of characters where any one of them counts as a match:
/[abc]/.test("apple") // true — "a" is in the set
/[abc]/.test("dog") // false — none of d, o, g are in {a, b, c}
A range shorthand avoids listing every character:
[a-z] // any lowercase letter
[A-Z] // any uppercase letter
[0-9] // any digit
[a-zA-Z0-9] // any letter (either case) or digit
A caret ^ as the first character inside brackets negates the set —
"any character except these":
/[^0-9]/.test("abc") // true — "a" is not a digit
/[^0-9]/.test("123") // false — every character is a digit
⚠️ Watch Out
^ means two completely different things depending on context: at the very
start of a whole pattern it's the "start of string" anchor from Lesson 2; as the
first character inside square brackets, it means negation. ^abc and
[^abc] look similar but mean opposite kinds of things.
Shorthand classes
Because digits, letters, and whitespace come up constantly, regex has built-in shortcuts:
\d — any digit (same as [0-9])
\w — any "word" character (same as [a-zA-Z0-9_])
\s — any whitespace (space, tab, newline)
\D — any NON-digit (same as [^0-9])
\W — any NON-word character
\S — any NON-whitespace character
The uppercase versions are simply the negation of the lowercase ones — an easy pattern to remember once you notice it.
Quantifiers: how many times
* — zero or more
+ — one or more
? — zero or one (optional)
{n} — exactly n times
{n,} — n or more times
{n,m} — between n and m times, inclusive
/colou?r/.test("color") // true — the "u" is optional
/colou?r/.test("colour") // true
/\d{3}/.test("12") // false — needs 3 digits, only 2 present
/\d{3}/.test("123") // true
/\d{2,4}/.test("12345") // true — it just needs 2 to 4 digits somewhere
Building up a realistic pattern
A simple US phone number like 555-123-4567: three digits, a dash, three digits,
a dash, four digits.
/^\d{3}-\d{3}-\d{4}$/.test("555-123-4567") // true
/^\d{3}-\d{3}-\d{4}$/.test("555-12-4567") // false — middle group is too short
Or a simple date like 2026-07-13: four digits, a dash, two digits, a dash, two
digits.
/^\d{4}-\d{2}-\d{2}$/.test("2026-07-13") // true
/^\d{4}-\d{2}-\d{2}$/.test("26-07-13") // false — year needs 4 digits, not 2
🙋 There Are No Dumb Questions
Q: Does \d{4}-\d{2}-\d{2} check that the date is actually
valid — like rejecting month 13 or day 40?
A: No — that pattern only checks the shape (digit counts and dash placement), not the
meaning. 9999-99-99 would still match. Regex is great at validating
format, but real value-range checks (month between 01 and 12, day valid for that month) usually
need actual code, or a much more elaborate pattern than is worth writing by hand.
🔧 Try It Yourself
Write a pattern that matches a simple hex color code like #a3f009 — a
# followed by exactly 6 characters, each being a digit or a letter a–f (case
insensitive is fine to ignore for now). Hint: [0-9a-fA-F] is the character class
you need, paired with a {6} quantifier.
✅ Quick Check
Which quantifier means "one or more"?
Next up: capture groups — pulling specific pieces out of a match, and backreferences for matching repeated patterns.