Lesson 3 of 6

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.

← Back to
Next →