Regular expressions are one of the most powerful text tools available, and also one of the easiest to get subtly wrong. A pattern that looks correct can quietly match too much, miss edge cases, or — in the worst case — hang your program on a specially shaped input. The difference between a regex you wrote and a regex you can trust is testing it against real examples, including the ones you expect to fail.
This guide covers how to build a pattern incrementally, the traps that cause the most bugs, and how to check your work before it goes anywhere near production.
Build the pattern from real examples first
Before writing any pattern, collect a handful of strings that should match and a handful that should not. This sounds obvious, but skipping it is the root of most regex bugs. Your should-match list defines what you are trying to capture; your should-not-match list is what stops the pattern from being too greedy.
Then build up the expression piece by piece, checking after each addition that the matches and non-matches still behave. A pattern assembled all at once and tested once is far more likely to hide a mistake than one grown a few characters at a time against a fixed set of examples.
Anchors decide where a match can happen
Without anchors, a pattern can match anywhere inside a string, which is a frequent source of surprise. If you want to validate that an entire string is a postcode, you need to anchor the pattern to the start and end; otherwise it will happily accept a valid postcode buried inside a line of junk. The start-of-string and end-of-string anchors turn "contains something matching" into "is exactly this," and forgetting them is why a validation regex sometimes lets through inputs it should reject.
Greedy versus lazy: the classic over-match
By default, quantifiers are greedy — they grab as much as they possibly can while still allowing the overall pattern to match. This causes the single most common regex surprise: a pattern meant to match one tag or one quoted string swallows everything up to the last closing tag on the line instead of the first.
The fix is usually to make the quantifier lazy (matching as little as possible) or to match "anything except the closing character" rather than "anything." When a pattern captures far more than you intended, greediness is the first thing to check.
Character classes are more literal than they look
Inside a character class, most special characters lose their special meaning, which trips people up in both directions. A dot inside a class is a literal dot, not "any character." A hyphen between two characters means a range, so putting one in the wrong place either creates an unintended range or has to be escaped or placed at the edge to be literal. Being precise about what a class actually contains prevents patterns that appear to work on your examples but accept unexpected characters in the wild.
Catastrophic backtracking can hang your program
This is the trap that turns a cosmetic bug into an outage. Certain patterns — typically nested quantifiers like a group that can repeat, inside another repetition, with overlapping possibilities — can take exponential time on inputs that almost match. A pattern that runs instantly on your test strings can freeze for seconds or minutes on a crafted input, which is a real denial-of-service risk if the regex runs on user-supplied data.
The defences are to avoid nested, ambiguous quantifiers, to prefer more specific character classes over broad "anything" matches, and to test with long near-miss inputs, not just short passing ones. If a regex engine offers a timeout, use it when the input is untrusted.
Test the failures, not just the successes
Most people test a regex by confirming it matches what they want. The more valuable test is confirming it rejects what it should. Feed it empty strings, strings that are almost valid, strings with leading or trailing spaces, and strings much longer than expected. Each of these has caused real bugs: a validator that accepts an empty field, a pattern that matches with a stray newline, or one that slows dramatically on a long input. A regex you can trust is one you have actively tried to break.
Comment and simplify anything non-trivial
A regex that took effort to write will be unreadable to you in six months and to anyone else immediately. If your engine supports extended or verbose mode, use it to add whitespace and comments explaining each part. If it does not, keep a plain-language note next to the pattern describing what it matches and, crucially, the examples it was built and tested against. Often the best outcome of writing a complex regex is realising a simpler pattern, or a small amount of ordinary code, would be clearer and safer.
Tools used in this workflow
Primary tool: Regex Tester →
Related: Ultimate Text Toolkit