So far, quantifiers like *, +, and {n,m} have only ever applied to a single preceding character or class — a+ means “one or more a,” \d{3} means “three digits.” But what if you want to repeat a whole sequence of characters, like ab, as a unit? That’s what parentheses are for.
Grouping a sequence
Wrapping part of a pattern in (...) turns it into a single group that a quantifier can apply to as a whole, instead of just to the one character immediately before it.
/ab+/.test("ababab"); // true, but for the wrong reason — matches "a" then "bbb"-style runs of b
/(ab)+/.test("ababab"); // true — the whole "ab" unit repeats three times
/(ab)+/.test("abc"); // true — matches just the leading "ab"
Notice the difference between ab+ and (ab)+: without parentheses, + only attaches to the b, meaning “one a followed by one-or-more b.” With parentheses, + attaches to the entire ab sequence, meaning “one-or-more repetitions of ab.”
The demo confirms it: (ab)+ matches ab (once), ababab (three repetitions), and just the ab prefix inside abc — each time treating ab as one repeatable chunk.
Grouping longer sequences
Groups aren’t limited to two characters — anything inside the parentheses, including character classes and other quantifiers, becomes part of the unit being repeated.
/(ha)+/.test("hahaha!"); // true — "ha" repeats three times before "!"
/(ha)+/.test("meh"); // false — no "ha" sequence at all
Here (ha)+ finds two separate matches: hahaha (three repetitions in a row) and ha (a single repetition later in the text) — but nothing in meh, since it contains no ha sequence to repeat.
Groups can be quantified with anything
Just like single characters, a group can be followed by any quantifier you’ve learned: (ab)? (optional), (ab){2,4} (2 to 4 repetitions), or combined with alternation inside the parentheses, like (cat|dog)+, to mean “one or more repetitions of either ‘cat’ or ‘dog.’”
/^(cat|dog)+$/.test("catdogcat"); // true — three repetitions, mixing both words
/^(cat|dog)+$/.test("catbird"); // false — "bird" isn't one of the allowed repeats
Tip
Parentheses do more than grouping, though — by default, every (...) group also captures the exact text it matched, so your code can pull that piece out afterward (think of extracting just the area code from a phone number, or just the year from a date). We haven’t used that capturing behavior yet on purpose — it, along with non-capturing groups (?:...) and named groups (?<name>...), is exactly what the next lesson covers in depth.
Try it yourself
Using the second demo above, change the pattern from (ha)+ to (ha){2,} and predict which matches disappear.
What should happen
(ha){2,} requires at least two repetitions of ha in a row, so hahaha (three reps) still matches, but the standalone ha (only one rep) no longer does — it fails to reach the minimum of 2. This is the same “grouping + quantifier” mechanism as (ab)+, just with a more precise repetition count.
What’s next
You now know that parentheses group and repeat a sequence — but every group you’ve written has also been silently capturing its matched text. The next lesson digs into capturing groups, non-capturing groups, and named groups, and how to extract exactly the pieces of a match you actually want.