When not to use regex

Recognise the inputs a regular expression cannot handle, and pick the parser, schema or state machine that can.

Structured and nested text

A regular expression recognises a regular language. Anything with matched nesting, escapes that change meaning, or a grammar with recursion is outside that class, which is why HTML and JSON patterns always end up either wrong or unmaintainable.

InputRegex attemptWhat to use instead
HTML or XMLan element pattern that breaks on nested tags and attributesan HTML parser or an XML parser with a path query
JSONextracting one field with a brace-matching patternJSON.parse, or a streaming parser for large documents
CSVsplitting on commas, which breaks on quoted fieldsa CSV reader that understands quoting and newlines
Source codematching a function body by counting bracesa real parser, an AST tool, or a language server
URLsa pattern that guesses at percent-encoding and userinfonew URL() or an equivalent URL parser
Configurationa hand-written line patternthe format's own loader, for example a YAML or TOML library
// the pattern people write, and the two rules it misses
const FIELD = /^([^,]+),([^,]+),([^,]+)$/;
FIELD.test('a,b,c');            // true
FIELD.test('"x,y",b,c');        // false: a quoted comma is legitimate CSV

// the parser that handles it
function parseLine(line) {
  const out = [];
  let field = '', quoted = false;
  for (const ch of line) {
    if (ch === '"') quoted = !quoted;
    else if (ch === ',' && !quoted) { out.push(field); field = ''; }
    else field += ch;
  }
  out.push(field);
  return out;
}
parseLine('"x,y",b,c');         // [ 'x,y', 'b', 'c' ]

Grammars, state machines and balanced input

  • Balanced brackets, matching quotes of any kind and nested blocks need a counter or a stack. A pattern with a backreference can match one fixed depth, never an arbitrary one.
  • When a rule is a sequence of states, write the states: tokenise, then run a small machine. It is longer than one pattern and dramatically easier to test and extend.
  • Two-step parsing is the practical middle ground: use a regex to split the input into lines or tokens, then a small function to interpret them. The pattern handles the easy part and the code handles the nesting.
  • For structured data, a schema validator turns an implicit format into an explicit contract. Validation errors become data instead of a failed match.
  • Languages that support recursion in patterns, such as PCRE with recursion and .NET with balancing groups, exist — and are almost never the right answer in application code.
# two-step: regex for the shape, code for the structure
import re, json

def extract_objects(text):
    """Regex finds candidate blocks; a real parser decides which are valid."""
    for m in re.finditer(r'\{[^{}]*\}', text):
        try:
            yield json.loads(m.group(0))
        except json.JSONDecodeError:
            continue          # a false positive from the pattern is simply skipped

list(extract_objects('noise {"a":1} more {"b": [2]}'))

A decision you can apply quickly

  • Is the input nested, recursive, or does it contain a string syntax with escapes? If yes, use a parser.
  • Is there a standard library or a well-maintained package for this exact format? If yes, use it and delete your pattern.
  • Is the pattern used as a security control? Write a test suite around it, or use a validator that is maintained by someone who owns that problem.
  • Is the pattern longer than roughly two lines? Rename it, comment it, and consider whether a parse step would be shorter.
  • Does the code silently accept a partial match where a full match was intended? Anchor it or switch to a full-match API before shipping.
⚠️
The cost of a regex is not the line count, it is the failure mode. A parser raises an error at the exact input that broke it. A regex returns null or, worse, a plausible wrong answer, and the bug surfaces three systems away.

FAQ

Can a regex match balanced parentheses?
Not in the general case. PCRE recursion and .NET balancing groups can do it for a specific grammar, but the result is a pattern most teams cannot maintain. Use a tokeniser and a counter.
Is a regex ever the right choice for HTML?
Only in narrow, trustworthy cases: pulling a single attribute out of a snippet you produced yourself, or finding a string in a log. For documents you did not generate, use an HTML parser and a selector.

Practical patterns: validation, logs and URLs Greedy, lazy and catastrophic backtracking

Last refreshed 2026-09-18.