Catastrophic backtracking: why a regex can pin a CPU
Nested quantifiers make the number of match attempts grow exponentially; (a+)+b can hang a process on one long non-matching input. Drop the nesting or put a timeout on it.
When a single regex freezes a service, it is usually catastrophic backtracking: the number of combinations the engine tries grows exponentially with input length.
The shape that breaks
/^(a+)+b$/.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
There is no b, so it can never match. But before giving up, the engine tries every way to split those a characters: the outer + and inner + can divide them freely, giving 2 to the n combinations. Thirty a characters is a billion attempts.
The warning sign is a quantifier inside a quantifier: (x+)*, (x*)*, or (x|y)+ where both branches match the same input.
Fix one: remove the nesting
That regex actually means “all a, then one b”:
/^a+b$/
A quantifier with no nesting is linear. Most catastrophic regexes are fine once hand-rewritten without nesting.
Fix two: make the group atomic
When you genuinely need a capture group, make the inner group atomic or possessive so the engine cannot backtrack into it:
/^(?:a++)+b$/ // possessive quantifier
JavaScript has lookbehind since ES2018, but atomic groups only arrive in ES2025, so rewriting is the usual route today.
Fix three: bound the input
If the domain never exceeds 200 characters, reject longer with an early return. Exponential growth stops being scary when the base is bounded.
Fix four: add a timeout
The sturdiest fallback is trusting no regex at all:
function safeTest(re, s, tag) {
const start = Date.now();
const worker = new Worker(/* run re.test(s) on its own thread */);
// on timeout, terminate and return false
}
Node has no built-in regex timeout, so isolation into a thread or process is the only option. Engines that do not backtrack, such as RE2, are immune by construction, at the cost of backreferences and lookbehind.
Checklist
| What you see | Risk |
|---|---|
(a+)* (a*)* |
high, exponential |
| `(a | a)*` overlapping branches |
| User-supplied regex | high, equals remote code |
| Fixed literal regex | low, no backtracking |
The last row is the most overlooked: never let users submit a regular expression. It is not a config value; it is a Turing-complete program that runs on your server.
A quantifier inside a quantifier is the alarm bell. If you cannot rewrite it flat, add a timeout.

Comments
…