What Is a Non-Capturing Group in Regex? (?:pattern) Explained Simply

Every developer who uses regex long enough eventually writes a pattern like this:

const regex = /(https?):\/\/([^/]+)(\/.*)?/;

It works. Capture groups extract the protocol, domain, and path.

But sometimes you do NOT want to capture everything.

Maybe you are grouping for alternation but the group itself is irrelevant. Maybe too many captures clutter your results. Maybe you just want cleaner code.

That is where non-capturing groups come in.

The syntax (?:...) groups patterns without storing the match in a capture group. It is one of those small regex features that makes a huge difference in real-world code.

If you want to experiment with these patterns, the Regex Tester shows exactly which groups capture and which do not.


What Is a Non-Capturing Group?

A non-capturing group uses (?:...) instead of (...).

(?:pattern)

It groups the pattern for quantifiers or alternation but does NOT store the matched text in a backreference.

Compare:

SyntaxTypeCaptures
(...)Capturing groupYes —stored as $1, $2, etc.
(?:...)Non-capturing groupNo

Why Non-Capturing Groups Exist

Capturing groups are not free.

Every captured group requires the regex engine to:

  • store the matched substring
  • maintain backreference state
  • allocate memory for the result

If you use a group only for logical grouping —like applying a quantifier or alternation —a capturing group wastes resources and clutters your result array.

Non-capturing groups solve that.


Basic Example: Quantifier on Alternation

Suppose you want to match "cats" or "cat":

const regex = /cats?/;

This works. But for longer words with shared prefixes:

const regex = /(?:apple)s?/;

Now it matches "apple" or "apples". The non-capturing group applies the s? to the entire word "apple" without storing it.


Example Without Non-Capturing Group

If you use a regular capturing group:

const regex = /(apple)s?/;
const match = "apples".match(regex);
console.log(match[1]); // "apple"

You get an unwanted capture. The group captures "apple" even if you only needed to match "apples" as a whole.


Example With Non-Capturing Group

const regex = /(?:apple)s?/;
const match = "apples".match(regex);
console.log(match[1]); // undefined

No capture. Cleaner results.


Real-World Use Case: URL Parsing

const url = "https://example.com:8080/path?q=1";

const regex = /(https?):\/\/([^/:]+)(?::(\d+))?(\/.*)?/;

const match = url.match(regex);

console.log(match[1]); // "https"
console.log(match[2]); // "example.com"
console.log(match[3]); // "8080"
console.log(match[4]); // "/path?q=1"

The port group (?::(\d+))? uses a non-capturing outer group. The colon before the port is grouped but not captured. Only the digits are captured in group 3.

This is a textbook use of non-capturing groups: grouping for optionality without unwanted captures.


Performance Impact

Non-capturing groups are faster than capturing groups.

The difference is small for a single match but compounds:

  • in loops
  • on large inputs
  • inside backtracking-intensive patterns
// Capturing —slower
const slow = /(?:abc)+/;

// Non-capturing —faster
const fast = /(?:abc)+/;

Actually, both examples here are identical. The real comparison:

// Capturing —slower, stores groups
/(\d{3})-(\d{3})-(\d{4})/

// Non-capturing for non-essential groupings
/(?:\d{3})-(?:\d{3})-(?:\d{4})/

If you do not need backreferences, non-capturing groups always win on performance.

Related reading: \d Is Less Efficient Than [0-9] —Node.js Regex Performance Deep Dive


Alternation Without Capturing

One of the most common uses:

const regex = /(?:cat|dog)s?/;

This matches "cat", "cats", "dog", "dogs" without capturing anything.

If you used (cat|dog)s?, every match would store "cat" or "dog" in group 1 unnecessarily.


Nesting Non-Capturing Groups

Non-capturing groups can be nested:

(?:https?:\/\/)?(?:[^\/\s]+)

Each non-capturing group does its job without storing matches.


JavaScript Example: Extracting Domains

const urls = [
  "https://google.com/search",
  "http://example.org",
  "ftp://files.example.com"
];

const regex = /(?:https?:\/\/)?([^\/\s]+)/;

urls.forEach(url => {
  const match = url.match(regex);
  console.log(match[1]);
});

The protocol part uses a non-capturing group. Only the domain is captured.


Python Example

import re

urls = [
  "https://google.com/search",
  "http://example.org",
  "ftp://files.example.com"
]

regex = re.compile(r'(?:https?://)?([^/\s]+)')

for url in urls:
    match = regex.search(url)
    if match:
        print(match.group(1))
# google.com
# example.org
# files.example.com

Same pattern, same benefit.


Non-Capturing Groups in Find and Replace

Non-capturing groups shine in replacement operations.

const text = "cats and dogs";
const regex = /(?:cat|dog)s?/g;
const result = text.replace(regex, "animals");
console.log(result); // "animals and animals"

No unwanted backreferences in the replacement string. Simple and clean.


Common Mistake: Accidentally Capturing

Developers new to non-capturing groups sometimes forget the ?: syntax:

// Accidentally capturing
const regex = /(https?):\/\/([^/]+)/;

When they only need group 2:

// Intentional —only group 2 captures
const regex = /(?:https?):\/\/([^/]+)/;

Small difference. Big impact on result clarity.

Related reading: Common Regex Mistakes Developers Keep Making


When to Use Capturing Groups Instead

Non-capturing groups are not always the right choice.

Use capturing groups when you NEED the backreference:

const regex = /(\w+)\s+\1/;  // backreference to group 1
console.log(regex.test("hello hello")); // true

Use non-capturing groups when:

  • grouping for alternation only
  • grouping for quantifiers only
  • wrapping optional prefixes/suffixes
  • you want clean result arrays

How Non-Capturing Groups Affect Backreferences

In JavaScript:

const regex = /(?:\w+)\s+(\w+)/;
const match = "hello world".match(regex);
console.log(match[0]); // "hello world"
console.log(match[1]); // "world"

Group 1 is "world" because the non-capturing group does not count toward backreference numbering.

Related reading: How to Access Regex Matched Groups in JavaScript —match vs exec vs matchAll


Non-Capturing Groups vs Atomic Groups

Atomic groups (?>...) are another variant. They prevent backtracking into the group.

Non-capturing groups (?:...) do NOT prevent backtracking. They only skip capturing.

They solve different problems.


FAQ

What is a non-capturing group in regex?

A non-capturing group (?:...) groups a pattern for quantifiers or alternation without storing the matched substring.


What is the difference between (...) and (?:...)?

Capturing group (..,) stores the match. Non-capturing (?:...) does not.


When should I use a non-capturing group?

Use non-capturing groups when you need grouping logic but do not need backreferences. This improves performance and keeps result arrays clean.


Do non-capturing groups improve performance?

Yes. They avoid the overhead of storing captured substrings. The improvement is small per match but adds up in loops.


Can I nest non-capturing groups?

Yes. (?:https?:\/\/)?(?:[^\/\s]+) is perfectly valid.


Do non-capturing groups prevent backtracking?

No. Only atomic groups (?>...) prevent backtracking. Non-capturing groups only skip capturing.


Are non-capturing groups supported in all regex engines?

Yes. The (?:...) syntax is widely supported across JavaScript, Python, PHP, Java, .NET, and most modern regex engines.


Final Thoughts

Non-capturing groups are one of those small regex features that experienced developers use instinctively.

The difference between:

/(http|https):\/\//

and:

/(?:http|https):\/\//

seems minor. But over time, non-capturing groups lead to:

  • cleaner match arrays
  • less confusion about group numbering
  • slightly better performance
  • more intentional code

The rule is simple: if you do not need the captured text, do not capture it.

If you want to test your own patterns and see exactly which groups capture, the Regex Tester visualizes groups clearly.

You may also find these related developer tools useful: