Regex Explainer

Explain a regex — and find out if it can hang

Paste a pattern for a plain-English breakdown, plus the checks that matter: nested quantifiers that backtrack catastrophically, missing anchors, unescaped dots, and ranges that span more than they look like.

/^(a+)+$/

Match a whole string consisting of one or more of (one or more of "a").

Do not run this against untrusted input — see below.

What to check

  • Dangerous

    Catastrophic backtracking — this pattern can hang

    (a+)+

    A quantifier repeats a group that itself contains an unbounded quantifier over an overlapping character set. On input that ALMOST matches, the engine has exponentially many ways to split the text between the two quantifiers and must try all of them before it can report failure. A few dozen characters can take minutes of CPU, which makes this a denial-of-service vector if the pattern ever runs against untrusted input.

    Fix: Remove one level of repetition. Usually the inner quantifier alone is enough: (a+)+ means the same thing as a+. Where the nesting is genuinely needed, make the inner part non-overlapping or use an atomic group in an engine that has one — JavaScript does not.

  • Note

    Anchors match string ends, not line ends

    Without the m flag, ^ and $ match only the very start and end of the input — not the start and end of each line. On multi-line input this is often not what was intended.

    Fix: Add the m flag if the pattern should apply per line.

Step by step

  1. ^

    Anchor to the start of the input.

  2. (a+)+

    Match one or more of (one or more of "a").

  3. $

    Anchor to the end of the input.

Details

Capture groups
1
Flags
None set

Worked examples

Built for agents too

Worth calling before shipping any pattern that runs against user input — the backtracking check is structural, so it does not depend on finding an input that triggers the hang.

curl 'https://regex-explainer.gumballtools.com/api/v1/explain?pattern=%5E(a%2B)%2B%24'

API and MCP setup · llms.txt

Questions people actually ask

What is catastrophic backtracking?

When a quantifier repeats a group that itself contains a quantifier over overlapping characters — (a+)+ being the canonical example — the engine can divide the input between the two quantifiers in exponentially many ways. On input that matches, it finds an answer immediately. On input that ALMOST matches, it must try every division before it can report failure. Thirty characters can take minutes. If the pattern runs against user input, that is a denial-of-service vulnerability, and it looks like ordinary code.

How do I fix a pattern that backtracks catastrophically?

Usually by deleting one level of repetition, because it was redundant: (a+)+ matches exactly the same strings as a+, just catastrophically slower. Where the nesting is genuinely needed, make the inner and outer parts match disjoint character sets so there is only one way to divide the input. Some engines offer atomic groups or possessive quantifiers for this; JavaScript has neither, so restructuring is the only option.

Why does my validation regex accept invalid input?

Almost always because it is unanchored. Without ^ and $, a pattern succeeds if it matches any substring, so an email pattern will happily accept "garbage a@b.co garbage". The pattern is not wrong about what an email looks like; it is answering "does this text contain one" when you meant "is this text one". Wrap the whole pattern in ^ and $.

Why does ^a|b$ not work the way it looks?

Alternation has the lowest precedence of any regex operator, so ^a|b$ parses as (^a)|(b$) — "starts with a, OR ends with b" — not "is exactly a or b". The anchors bind to their own branch only. Group the alternation to get the intended meaning: ^(?:a|b)$.

Why is [A-z] a bug?

Character ranges follow character codes, not the alphabet. Between uppercase Z (90) and lowercase a (97) sit six punctuation characters: [ \ ] ^ _ and the backtick. So [A-z] matches all of those too. Write what you mean: [A-Za-z].

What is the difference between greedy and lazy quantifiers?

A greedy quantifier takes as much as it can and gives back only when forced; a lazy one (marked with a trailing ?) takes as little as possible and expands only when forced. The classic case is <.+> against "<a><b>": greedy matches the whole string, because .+ swallows everything up to the last >. The lazy <.+?> matches just "<a>". Neither is faster in general — laziness changes what matches, not the cost.

Does this tool run the regex against test input?

No, deliberately. It analyses the pattern structurally, which is how it can tell you a pattern is a backtracking risk without needing to find an input that triggers it — executing a vulnerable pattern to discover it is vulnerable is exactly the thing you are trying to avoid. Use a scratch file or a REPL if you want to test matches.

Which regex dialect does it understand?

JavaScript. That covers most of what people write, including named groups, lookahead, and lookbehind. Constructs that exist in PCRE, Perl, or .NET but not JavaScript — possessive quantifiers, atomic groups, recursion — are reported as unsupported rather than silently mis-explained, because a confident explanation of the wrong syntax is worse than none.

Can an AI agent call this directly?

Yes, and it is worth doing before shipping any pattern that touches user input. There is a JSON API at /api/v1/explain, an MCP server at /api/mcp exposing an explain_regex tool, an OpenAPI document at /.well-known/openapi.json, and a machine-readable index at /llms.txt. Every page is also available as markdown at the same URL with an Accept: text/markdown header.