Why is my regex not matching
Tools Nimbus is a free, no-signup developer toolkit that runs entirely in your browser, so your data is never uploaded to a server. A regex usually fails to match because the global flag advanced its lastIndex, a special character was not escaped, or a flag such as ignore-case was off. Paste your pattern into the Tools Nimbus Regex Tester to see live matches, capture groups, and flag toggles, free with no signup.
Last updated June 2026
Start by isolating the failure
A regex that does not match is almost never random. It is one of a small set of well-known traps: a flag that is on (or off) when it should not be, a metacharacter being read literally or a literal being read as a metacharacter, or invisible characters in the text. The fastest way to debug is to separate the pattern from the surrounding code. Drop the raw pattern and a sample of the real text into the Regex Tester, which uses the same JavaScript engine as your browser and Node.js, and watch which parts highlight. If it matches there but not in your code, the bug is in how your code calls the regex, not in the pattern itself.
Symptom, cause, and fix at a glance
| Symptom | Most likely cause | Fix |
|---|---|---|
Matches once, then test() returns false | Global flag advancing lastIndex on a reused regex | Reset lastIndex = 0, or use match() / a fresh regex |
| A literal dot, plus, or bracket never matches | Metacharacter being read as a special token | Escape it: \., \+, \[ |
| Misses uppercase or lowercase variants | Case-sensitive by default | Add the i flag |
| Does not span multiple lines | . excludes newline; ^$ anchor the whole string | Add s for dotAll, and/or m for per-line anchors |
Works as a literal, breaks via new RegExp | Backslashes eaten by the string | Double them: "\\d+" |
| Identical-looking text still fails | Hidden characters: \r\n, non-breaking space, zero-width | Normalize whitespace; inspect in a tester |
Cause 1: the global flag and lastIndex (the sneaky one)
This is the bug that makes a regex appear to work intermittently. When a regex has the g flag, the methods test() and exec() are stateful: each call stores where it stopped in a lastIndex property and the next call resumes from there. If you keep one regex object around and call test() in a loop or across inputs, every other call can return false even though the string clearly matches.
const re = /cat/g;
re.test("cat"); // true, lastIndex now 3
re.test("cat"); // false, resumed from index 3
re.test("cat"); // true againThe fix is to not carry state. Use a literal without g for a simple yes/no check, reset re.lastIndex = 0 before each call, or switch to String.prototype.match() and String.prototype.matchAll(), which do not mutate the regex.
Cause 2: unescaped metacharacters
The characters . + * ? ( ) [ ] { } ^ $ | and the backslash all have meaning in a pattern. A common mistake is writing /3.14/ to match the string 3.14; the dot is a wildcard, so it also matches 3x14 and fails to be the precise check you wanted. To match a literal, escape it: /3\.14/. The reverse trap is just as common: when you build a pattern from user input or a filename, special characters in that input silently change the pattern. Escape the dynamic part first, for example by replacing every metacharacter with its escaped form before constructing the regex.
Cause 3: the wrong flags
Flags are the single most common reason a pattern that looks right does not match. Three matter most:
imakes matching case-insensitive. Without it,/error/never matchesError.gfinds every match instead of just the first. If you only get one result, you probably forgotg(but see Cause 1 for its side effects).mandscontrol multi-line text.mmakes^and$match at each line break;s(dotAll) lets.match newline characters. They are independent, and a pattern that needs to cross lines usually needss.
The Regex Tester exposes g, i, m, s, u, and y as toggles, so you can flip one on and watch the match set change without editing your code.
Cause 4: string-built patterns and double escaping
A pattern that works as a literal can break the moment you move it into a string for new RegExp(). The string parser consumes backslashes before the regex engine ever sees them, so "\d+" becomes the literal text d+. You have to double every backslash:
/\d{3}/ // literal: matches three digits
new RegExp("\d{3}") // wrong: matches "ddd"
new RegExp("\\d{3}") // correct: matches three digitsWhen in doubt, prefer a literal regex. Reserve new RegExp() for cases where the pattern is genuinely dynamic, and remember the extra backslashes when you do.
Cause 5: invisible characters in the text
Sometimes the pattern is perfect and the text is the problem. Text copied from a PDF, a spreadsheet, or a chat app often contains a non-breaking space (U+00A0) that looks exactly like a normal space but is a different code point, so / / misses it. Windows line endings (\r\n) leave a stray carriage return that $ may not sit where you expect. Zero-width characters and a trailing newline cause the same kind of silent miss. Normalize the input first, for example collapsing whitespace, before matching. Pasting the real text into a tester makes these gaps visible because you can see exactly where the highlight stops.
A note on Unicode
By default a regex works on UTF-16 code units, not whole characters, so . can match half of an emoji or an astral-plane character and produce broken results. Add the u flag for correct Unicode handling. Note too that \w and \d only cover ASCII, so accented letters and non-Latin digits will not match \w unless you use explicit Unicode property escapes such as \p{L} together with the u flag.
Diagnose it in one step
You do not have to guess. Open the Regex Tester, paste your exact pattern and a representative slice of the real text, and toggle flags one at a time. It runs entirely in your browser, so log lines, tokens, or customer data you are testing against never leave your machine. If the text came out of a JSON payload, clean it up first with the JSON Formatter, or decode an encoded value with the URL Encoder and Decoder before matching. For more developer fixes, browse the Tools Nimbus guides.
How to prevent it
- Decide your flags deliberately. Most bugs come from a missing
i,s, orm, or an unwantedg. - Never call stateful
test()orexec()on a shared global regex inside a loop without resettinglastIndex. - Escape any user-supplied text before building a pattern from it.
- Prefer literal regexes; only reach for
new RegExp()when the pattern is dynamic, and double your backslashes when you do. - When a match fails on text that looks correct, suspect invisible characters and test in the Regex Tester before changing code.