Regex tester · Guide

Regex Balancing Groups: Matching Nested Structures in .NET

Regular expressions provably cannot count, so matching balanced nesting is outside what regex is supposed to do. .NET does it anyway with balancing groups — a capture stack bolted onto the engine.

The syntax

Three constructs work together: (?<name>...) pushes onto a stack, (?<-name>...) pops, and (?(name)fail) checks whether anything is left:

Regex
^[^()]*(?:(?<open>\()[^()]*|(?<-open>\))[^()]*)*(?(open)(?!))$

Reading it: consume non-parens; then repeatedly either push on ( or pop on ); at the end, (?(open)(?!)) fails if the stack is non-empty. A ) with an empty stack fails the pop, so unbalanced input is rejected in both directions.

Extracting the content

A named group between the push and pop captures what was inside, so you can pull out the balanced section rather than just validating it:

C#
var re = new Regex(@"\((?<content>(?:[^()]|(?<open>\()|(?<-open>\)))*(?(open)(?!)))\)");
var m = re.Match("f(a(b)c)d");
m.Groups["content"].Value;   // "a(b)c"

What other engines offer

PCRE and Perl have recursion — \((?:[^()]|(?R))*\) — which is shorter and also unreadable. The regex module in Python supports it; the standard re module does not.

JavaScript, Java and Go have nothing equivalent. A pattern using balancing groups is .NET-only and will not survive a port.

When to stop

These patterns are write-only. Six months later nobody, including the author, can safely modify one, and a small requirement change means rewriting from scratch.

A counter loop over the characters is a few lines, runs in linear time, handles error reporting, and any colleague can read it. Use balancing groups when the pattern must live in a configuration value that only accepts a regex — that is genuinely the case they earn their keep in.

Frequently asked questions

Do balancing groups work outside .NET?

No. PCRE and Perl offer recursion instead; most other engines have neither.

Are they slow?

They backtrack heavily and can degrade badly on malformed input. Set a Regex timeout if the input is untrusted.

Should I use one?

Only when a regex is the only thing the surrounding system accepts. Otherwise write a counting loop or a parser.

Ready to try it?

Open the free browser-based Regex tester and apply what you just read — no sign-up, runs locally.

Open the Regex tester tool