A regular expression that is wrong rarely announces itself. It does not throw an error or fail to compile. It matches most of what you point it at, ships, and then behaves oddly on the two percent of inputs nobody put in the test file. By the time somebody notices, the pattern has been copied into three other places.
These are five patterns that look correct to a careful reader and are not, along with what they actually do. All of them are worth pasting into a tester with a deliberately awkward sample before you trust them.
1. The email pattern that rejects real addresses
Something like \b\w+@\w+\.\w+\b is the standard first attempt, and it matches the addresses you thought of while writing it. It rejects a great many valid ones, because \w covers letters, digits and underscore — and nothing else.
So it fails on first.last@example.com (dot in the local part), on user+tag@example.com (plus addressing, which people genuinely use), on me@mail.example.co.uk (subdomain and multi-part suffix), and on anything with a hyphen in the domain. Meanwhile it happily accepts a@b.c.
The deeper problem is the goal. The specification for what constitutes a valid address is genuinely baroque, and every regex that tries to implement it is either wrong or unreadable. The practical approach is to match loosely — something present, an @, something after it with a dot — and then establish that the address exists by sending mail to it. A confirmation email is the only real validator.
2. The pattern with no anchors
You write \d{3}-\d{4} to check a reference number. It matches. It also matches inside corrupted-999-1234-junk, because without anchors a pattern asks does this appear anywhere, not is this the whole thing.
If the string should be exactly the pattern, anchor both ends with ^ and $. If it should be a standalone token inside longer text, use word boundaries: \b\d{3}-\d{4}\b. Choose deliberately, because the two mean different things and the difference only shows up on malformed input — which is exactly the input you were validating against.
3. Greedy quantifiers that swallow the line
Given <a> text <b>, the pattern <(.*)> does not capture a. It captures a> text <b, because .* takes as much as it can and then hands back only what it must.
Two fixes. The lazy quantifier <(.*?)> takes as little as possible. Better still, say what you actually mean with a negated class: <([^>]*)> — everything that is not a closing bracket. The negated class is faster and it survives being copied into a context where the flags differ.
A related warning: neither fix makes this a safe way to parse HTML or nested structures. Regular expressions cannot count nesting. For a document format, use a parser.
4. Nested quantifiers that hang the process
This one is not merely wrong; it is a denial-of-service waiting for the right input. Patterns shaped like (a+)+$, (\s+)+, or (\w+\s?)+ put one quantifier inside another over characters that overlap. The engine has an exponential number of ways to divide the input between the inner and outer repetition, and on a non-matching string it will try a great many of them.
Twenty characters can take minutes. Thirty can take longer than the process will live. If that pattern runs on user-supplied text on a server, a single request can take the service down.
The tell is a group ending in + or * which itself contains + or *. When you see it, rewrite so that only one thing repeats, and test with a long string that almost matches but fails at the very end — that is the worst case, and it will not appear in your happy-path samples.
5. Assumptions about newlines
Two defaults surprise people constantly. The dot does not match a newline, so a pattern meant to capture a block spanning several lines matches nothing until you add the dotall flag. And $ means the end of the whole string, not the end of each line, until you add the multiline flag.
The correction has its own trap. Once dotall is on, .* will run past every line break to the end of the input, which is a much bigger version of problem 3. If you want "to the end of this line", [^\n]* says so regardless of flags.
Our Regex Tester puts the flags on checkboxes next to the pattern and highlights every match as you type, which makes this category obvious in seconds — you toggle a flag and watch the highlighting jump. It runs in your browser, so the log or export you are testing against is never uploaded.
The habit that catches all five
Keep two samples, not one. Everybody assembles a list of strings the pattern should match. Almost nobody writes the list of strings it should not — the near-misses, the empty string, the one with a trailing space, the one that is six thousand characters of noise.
The negative list is where these five failures live. It takes five minutes to write and it is the difference between a pattern that works and a pattern that has not been contradicted yet.
Common questions
Should I validate email addresses with a regular expression at all?
For catching obvious typos in a form, yes — a loose pattern gives instant feedback. For deciding whether an address is real, no. Send a confirmation message; that is the only check that answers the actual question.
My pattern works in the tester but not in my language.
Flavours differ. The tester uses the JavaScript engine, so patterns behave as they do in Node and the browser. Named groups, lookbehind, and Unicode property escapes have different syntax and different availability elsewhere, and escaping rules inside string literals catch people out — a backslash often needs doubling.
How do I tell whether a pattern is dangerously slow?
Feed it a long string that matches the first ninety percent and then fails. If matching time grows sharply as you lengthen that string, the pattern backtracks badly. Linear growth is fine; anything that doubles when you add a few characters is not safe to run on input you did not write.
More guides
- How to Tell if a Website Is Really Uploading Your Files
- How Much House Can You Actually Afford?
- How to Make a Passport Photo From a Phone Picture
- How to Send Only the Pages You Need From a PDF
- How to Merge Scanned Pages Without the Order Going Wrong
- What You're Actually Pasting Into an Online JSON Formatter
- Why Two Names That Look Identical Don't Match
- camelCase, snake_case or kebab-case: Which Goes Where
- Asking AI for the First Time: What to Ask, and What Not to Paste