Regex non-capturing groups explained
A plain pair of parentheses in a regular expression does two jobs at once: it groups a sub-pattern so a quantifier can apply to the whole thing, and it captures whatever matched so you can read it back later. Often you only want the first job. That is what a non-capturing group is for.
The syntax
You write a non-capturing group as (?:...). The ?: right after the opening parenthesis tells the engine to group without capturing. Compare these two patterns against abcabc:
(abc)+ captures "abc", group 1 available
(?:abc)+ groups abc for the +, captures nothing
Both match the same text. The difference is only in what the engine stores and exposes afterward.
Why it matters for numbering
Capturing groups are numbered left to right by the position of their opening parenthesis. Every plain ( claims the next number. Non-capturing groups are skipped in that count, which keeps your important groups on stable, predictable numbers.
Consider a date pattern where you only care about the year:
(?:\d{2})/(?:\d{2})/(\d{4})
Here the day and month are grouped for clarity but not captured, so the year is group 1, not group 3. If you later use $1 in a replacement or \1 as a backreference, you are pointing at the year without having to count past throwaway groups. Add or remove a non-capturing group and your real capture numbers do not shift.
| Pattern | Group 1 | Group 2 |
|---|---|---|
(\d{2})/(\d{2})/(\d{4}) | day | month |
(?:\d{2})/(?:\d{2})/(\d{4}) | year | none |
This stability is the main practical reason to reach for (?:...). Named groups, written (?<name>...), solve the same problem from the other direction by letting you refer to a group by name instead of a position, but non-capturing groups remain the lightest tool when you need grouping and nothing more.
Performance
The performance difference is usually small, but it is real. A capturing group forces the engine to record the start and end offsets of every match attempt for that group, including during backtracking. In a hot pattern that backtracks heavily, or one applied across a large input, skipping that bookkeeping with (?:...) saves work and memory. It also makes the intent obvious to the next reader: this group is structural, not something I plan to extract.
A note on atomic groups
While we are on grouping, atomic groups are worth knowing. Written (?>...), an atomic group matches its contents and then throws away all the backtracking positions inside it. Once the group has matched, the engine will never re-enter it to try a different split. This is a tool against catastrophic backtracking, where a pattern like (a+)+$ can hang on certain inputs because the engine explores an exponential number of ways to divide the same characters. Wrapping the repetition atomically, as in (?>a+)+, removes those redundant paths. JavaScript added atomic groups and possessive quantifiers in recent versions, but support still varies, so confirm your target engine before relying on them.
The quickest way to see group numbering in action is to watch the capture table update as you edit a pattern. Try switching a group between (...) and (?:...) in the Regex Tester and see which captures appear.