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.
| Input | Regex attempt | What to use instead |
|---|---|---|
| HTML or XML | an element pattern that breaks on nested tags and attributes | an HTML parser or an XML parser with a path query |
| JSON | extracting one field with a brace-matching pattern | JSON.parse, or a streaming parser for large documents |
| CSV | splitting on commas, which breaks on quoted fields | a CSV reader that understands quoting and newlines |
| Source code | matching a function body by counting braces | a real parser, an AST tool, or a language server |
| URLs | a pattern that guesses at percent-encoding and userinfo | new URL() or an equivalent URL parser |
| Configuration | a hand-written line pattern | the 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.
Related
Practical patterns: validation, logs and URLs Greedy, lazy and catastrophic backtracking
Last refreshed 2026-09-18.