Character classes, quantifiers and anchors

Build patterns out of ranges, shorthands and counted repeats, and control exactly where a match is allowed to start and end.

Classes: ranges, negation and shorthands

A character class matches exactly one character from a set. Everything inside the square brackets is a set, never a sequence, so [abc] matches one letter and never the three-character string abc.

ClassMatchesWatch out
[a-z]one lowercase ASCII letterranges follow code-point order; [a-Z] is an error
[^a-z]one character outside the seta caret negates only in the first position
\d / \Da digit / a non-digit\d is ASCII-only even with the Unicode flag
\w / \Wa word character / its negationit is [A-Za-z0-9_]: underscore included, accented letters excluded
\s / \Swhitespace / non-whitespacein JavaScript the Unicode flag adds further space separators
[.]a literal dota dot is already literal inside a class; escaping it is harmless
const code = /^[A-Z]{2}-\d{4}$/;       // two letters, a dash, four digits
code.test('AB-1042');                    // true
code.test('ab-1042');                    // false, lowercase
code.test('AB-10422');                   // false, the $ anchor rejects the extra digit

const token = /[^\s,;]+/g;               // runs of non-delimiter characters
'a, b ;; c'.match(token);                // [ 'a', 'b', 'c' ]

/^[\w.+-]+@[\w-]+\.[\w.]+$/.test('[email protected]');   // true
⚠️
A literal hyphen must be first, last, or escaped: [a-z] is a range while [-az] and [a\-z] are the three characters. A misplaced hyphen is the quietest bug in regex, because the pattern still compiles and still matches — just not what you meant.

Quantifiers: greedy, lazy and counted

A quantifier applies to the single token on its left. A greedy quantifier takes as much as it can and then gives characters back; a lazy one takes as little as possible and then takes more. Both find a match when one exists — they differ in which match you get.

const html = '<b>one</b> and <b>two</b>';

html.match(/<b>.*<\/b>/)[0];     // '<b>one</b> and <b>two</b>'   greedy
html.match(/<b>.*?<\/b>/)[0];    // '<b>one</b>'                  lazy
html.match(/<b>[^<]*<\/b>/)[0];  // '<b>one</b>'                  negated class

/<\d{2,4}>/.test('<123>');       // true, two to four digits
/<\d{2,4}>/.test('<1>');         // false
/colou?r/.test('color');          // true, the u is optional
/^ab*$/.test('a');                // true, a plus zero b
  • * is zero or more, + is one or more, ? is zero or one. ab* matches a lone a; ab+ does not.
  • {n} is exact, {n,} is at least n, {n,m} is a range. No space is allowed inside the braces, and a malformed brace is treated as a literal brace by some engines rather than as an error.
  • Appending ? makes a quantifier lazy. It does not make the pattern faster in general; it changes which of several valid matches is returned.
  • A quantifier can only repeat one token. To repeat a sequence, wrap it in a group: (?:ab)+, not ab+.
  • When you want everything up to a delimiter, a negated class such as [^<]* is usually faster and clearer than a lazy dot-star.

Anchors and word boundaries

Anchors assert a position instead of consuming a character. They are the difference between matching a fragment somewhere in the subject and matching the subject itself.

/^\d{5}$/.test('12345');       // true, the whole string
/^\d{5}$/.test('12345 ');      // false, a trailing space
/\d{5}/.test('12345 ');        // true, no anchors, so a fragment is enough

/^\d+$/m.test('x\n12345');     // true: with m, ^ and $ also match at line breaks

/\bcat\b/.test('a cat sat');   // true
/\bcat\b/.test('concatenate'); // false, no boundary inside the word
/\bcat\b/.test('cat-like');    // true, a hyphen is a non-word character
AnchorThe position it asserts
^start of the string, or of a line when the m flag is set
$end of the string, or of a line when m is set
\A / \zabsolute start and end — PCRE, Python and .NET, not JavaScript
\b / \Ba word boundary, or a position that is not one
(?<=\n)after a newline, a portable stand-in for a multiline caret
  • An empty pattern with anchors is a valid test: /^$/.test('') is true, which is how you detect a blank line.
  • \b depends on the definition of a word character, so it behaves differently next to an accented letter or a CJK character. Assert on classes you control when that matters.
  • Anchoring both ends is what turns a pattern into a validation rule. A pattern without anchors is a search.

FAQ

Why does my pattern match a substring of a longer word?
There are no anchors. Add \b for word boundaries or ^...$ when the whole string must match. Most validation failures are missing anchors, not a wrong pattern body.
Is the lazy quantifier always safer?
No. Lazy changes what is matched, not whether the pattern is efficient, and a lazy dot-star still backtracks on failure. Prefer a negated character class when the delimiter is known.

Groups, captures and backreferences Greedy, lazy and catastrophic backtracking

Last refreshed 2026-09-18.