The ? quantifier: optional, and its two other jobs
? means zero or one of the preceding token. The same character does two other jobs in a pattern: it makes another quantifier lazy, and after an opening parenthesis it selects a group type.
At a glance
- Syntax
- ?
- Equivalent to
- {0,1}
- Greedy
- yes, it prefers to match one
- Other roles
- lazy modifier after a quantifier, and group prefix after an open parenthesis
- Optional capture group
- yields undefined when it did not participate, not an empty string
- Lazy form
- ??, which prefers to match zero
In detail
As a quantifier the question mark is the least surprising thing in regular expressions: the token before it may appear once or not at all. /colou?r/ matches both spellings, and /https?:\/\// matches both schemes. Because it is greedy it prefers the version with the character present, which is almost always what you want.
The confusion comes from the other two jobs. After another quantifier it is a modifier that flips greedy to lazy, so *? and +? and {2,4}? are single operators rather than a quantifier followed by an optional one. After an opening parenthesis it selects a group type, giving (?:, (?=, (?!, (?<= and (?<name>. Three meanings for one character is why a dense pattern is hard to read at a glance.
The result shape matters when the optional part is a capture group. A group that did not participate is undefined, not an empty string, so code that does m[1].length throws rather than reporting zero. Using a default when reading the group is the fix, and it is the difference between a pattern that handles missing input and one that crashes on it.
There is one more trap in combination. An optional group followed by another optional part can match the empty string overall, which means the pattern succeeds at every position. If a pattern of nothing but optional pieces is returning matches everywhere, that is why.
Examples
/colou?r/.test("color"); // true
/colou?r/.test("colour"); // true
/^https?:\/\//.test("http://x"); // true
/^https?:\/\//.test("https://x"); // trueconst m = /^(a)?(b)$/.exec("b");
m[0]; // "b"
m[1]; // undefined
m[2]; // "b"Things that catch people out
- A capture group that did not participate is undefined. Calling a string method on it throws, so read it with a default rather than assuming an empty string.
- The question mark after another quantifier is a lazy modifier, not a second optional. Reading /a+?/ as one or more, optionally, gets the behaviour backwards.
- A pattern built entirely from optional parts can match the empty string, so it succeeds at every position and a global match returns a long list of nothing.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →Lazy quantifiers: *?, +?, ?? and {n,m}?
A trailing question mark makes a quantifier prefer the shortest match. It fixes the overreaching tag pattern, and a negated class often fixes it better still.
(...) capturing groups: numbering and what comes back
How capture groups are numbered, what a group that did not participate returns, and why quantifying a group throws away everything but the last pass.
{n,m}: bounded repetition between two counts
A bounded count is inclusive at both ends and greedy toward the maximum. JavaScript has no {,m} shorthand, and writing one produces literal text.
{n}: matching exactly n repetitions
An exact count is the clearest quantifier there is, until it is applied to a group, at which point the capture keeps only the final iteration.
{n,}: at least n repetitions, with no upper bound
The open ended count covers everything * and + cannot express, and a stray space after the comma turns the whole quantifier into literal text.
Atomic groups in JavaScript: emulating them with a lookahead
JavaScript has no (?>...) and no possessive quantifiers. The lookahead and backreference pair gives you the same no backtracking behaviour.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links