Regular Expressions
Regex syntax and performance depend on the engine. Check which engine your language or tool uses.
Two engine families
Automata-based engines track possible matches as they read the input. Engines such as RE2 restrict features to provide predictable matching time.
Backtracking engines try alternatives and revisit earlier choices after a failure. They support more features, but ambiguous patterns can take a long time on some inputs.
Where the regular languages end
Backreferences compare new text with an earlier capture and can describe patterns beyond regular languages. Lookaround checks surrounding text without consuming it; support varies by engine.
Some engines support recursion. For structured formats such as JSON or source code, a parser is usually easier to maintain.
Catastrophic backtracking, concretely
Nested or overlapping repetitions can create many possible matches. (a+)+$ can be expensive on a long run of a followed by a character that makes the match fail.
Remove ambiguity where possible. Atomic groups and possessive quantifiers can prevent backtracking in engines that support them. Test failing inputs as well as successful ones.
Details that bite
- Greedy quantifiers try longer matches first; lazy ones try shorter matches first.
.usually excludes newlines unless dotall mode is enabled.- Multiline mode changes the meaning of
^and$. Some engines allow$before a final newline. - Unicode character classes and case folding vary by engine and flags.
Use the engine's full-match operation when validating a whole string. Use verbose mode if a pattern needs comments to remain readable.
Captures and replacement
Capturing groups save part of a match. Non-capturing groups organise a pattern without producing a capture. Named groups can make extraction clearer when the engine supports them.
Replacement syntax is separate from pattern syntax. A dollar sign or backslash in a replacement may mean a group reference rather than literal text. Check both escaping layers when the pattern is also inside a language string.
Matching deliberately
Decide whether you want a search, a match at the beginning, or a match of the entire input. Decide whether whitespace and case matter. Set those rules explicitly rather than relying on defaults.
When extracting several fields, inspect optional and unmatched groups. An absent capture and an empty capture can have different meanings.
Keeping patterns maintainable
Name the pattern after the format it recognises. Keep tests for accepted and rejected inputs, including long near-matches. Put complicated matching logic behind a small interface so callers do not each invent their own variant.
If maintaining the pattern requires remembering nested grammar rules, consider a parser. See Testing for generating inputs that explore awkward boundaries.
Reading
- RE2 syntax
- Russ Cox on regex engines
- regex101 for inspecting matches