Not every pair of parentheses needs to capture. Sometimes you just want to group part of a pattern — to apply a quantifier to a sequence, or to scope an alternation — without adding another numbered group to keep track of. That’s what (?:...) is for: a non-capturing group.
The syntax
A non-capturing group looks almost identical to a normal group, with ?: right after the opening parenthesis:
/(?:ab)+/ // groups "ab" for the + quantifier, captures nothing
/(?:https?|ftp)/ // groups an alternation, captures nothing
Everything else about how it matches is identical to a capturing group — it just doesn’t show up in match[1], match[2], etc., and it doesn’t shift the numbering of any capturing groups that come after it.
A common real-world case is matching one of several URL schemes, where you don’t care about which scheme matched, only about the URL as a whole:
const text = "Visit https://example.com or ftp://files.example.com today";
const urls = text.match(/(?:https?|ftp):\/\/\S+/g);
console.log(urls); // ["https://example.com", "ftp://files.example.com"]
Keeping numbering clean
The most practical reason to reach for (?:...) is to keep your capturing groups meaningful when a pattern also needs grouping for structure. Consider matching a title followed by a name, where you only want the name:
const str = "Mrs. Smith";
const withCapture = str.match(/(Mr|Mrs|Ms)\. (\w+)/);
console.log(withCapture[1]); // "Mrs" — the title, group 1
console.log(withCapture[2]); // "Smith" — the name is now group 2
const withoutCapture = str.match(/(?:Mr|Mrs|Ms)\. (\w+)/);
console.log(withoutCapture[1]); // "Smith" — the name is group 1
Making the title non-capturing means the name — the only thing you actually want — lands at match[1] instead of match[2]. It also avoids returning a title string you were never going to use.
Notice “Dr. Who” doesn’t match at all — the alternation only lists Mr, Mrs, and Ms, so a title outside that list is correctly rejected.
Capturing vs. non-capturing, at a glance
Capturing (...) |
Non-capturing (?:...) |
|
|---|---|---|
| Groups for quantifiers/alternation | Yes | Yes |
Appears in match[n] |
Yes | No |
| Shifts numbering of later groups | Yes | No |
Supports backreferences (\1) |
Yes | No — nothing to reference |
| Use when… | You need the matched text afterward | You only need the grouping, not the text |
Tip
As a rule of thumb: if you never write match[n] or \n for a particular group, make it non-capturing. It signals intent to anyone reading the pattern later — including future you.
Group without capturing
You want to match a price that’s either in dollars or euros, like $19.99 or €19.99, but you only care about capturing the numeric amount — not which currency symbol was used.
Show solution
const pattern = /(?:\$|€)(\d+\.\d{2})/;
console.log("$19.99".match(pattern)[1]); // "19.99"
console.log("€19.99".match(pattern)[1]); // "19.99"(?:\$|€) groups the two currency symbols as alternatives without capturing either one, so match[1] is always the amount, regardless of which currency was used.
What’s next
Non-capturing groups drop the capture entirely — but sometimes you do want to keep the captured text, just with a more descriptive label than a number. Next: named groups.