Regex Tester
Expected result: Two dates match: 2026-10-02 and 2026-11-15. With replacement enabled, the result is: Releases: 02/10/2026, 15/11/2026.
Limitations: Uses JavaScript RegExp, not PCRE. This pattern finds a date shape; it does not validate calendar dates. Worker execution is limited to 1.5 seconds.
Nothing you enter here is stored, logged or shared.
Write a pattern and see every match highlighted as you type, with capture groups and positions. Matching runs in a background thread, so even a catastrophically backtracking pattern will not freeze the page.
How to use
- Enter your pattern and toggle the flags you need.
- Paste the text to test against; matches highlight instantly.
- Tick Replace to preview a substitution using $1 or $<name>.
What regular expressions cannot do
A regular expression matches patterns, not structure. It has no memory of how deep it is inside something, which means anything with arbitrary nesting is genuinely outside what it can express: balanced brackets, nested quotes, HTML, JSON, source code.
Patterns that appear to work on these are matching a common case and failing on the rest, usually silently. The HTML tag pattern that works until an attribute contains a > . The comment stripper that eats the middle of a string literal. The CSV splitter that breaks on the first quoted comma. Each of these is a bug that surfaces months later on somebody else's data.
The rule of thumb is that if the thing you are matching can contain itself, you want a parser. Regex is excellent for the layer below that: validating a field, pulling values out of a known log format, finding and replacing across a codebase, splitting on a delimiter that cannot appear inside the parts.
Greedy, lazy, and where backtracking comes from
Quantifiers are greedy by default: .* takes as much as it can and then gives characters back one at a time until the rest of the pattern fits. So <.+> against <b>text</b> matches the whole string rather than the first tag, because the greedy .+ swallows everything and the final > is satisfied by the last one in the input.
Adding a question mark makes a quantifier lazy - <.+?> takes as little as possible and matches just <b>. Lazy is usually what people mean, and a character class is usually better than either: <[^>]+> says what you actually want, cannot cross a tag boundary at all, and does not backtrack.
That last point is the practical reason to care. Backtracking is what makes a pattern slow, and nesting one quantifier inside another - (a+)+, (\s*\w+)* - makes the number of paths the engine explores grow exponentially. On input that nearly matches, a pattern like that can run for minutes. This tester runs matching in a worker and kills it after a second and a half precisely because writing such a pattern is a normal thing to do by accident.
Flavour differences that will bite
The engine here is your browser's, so results match JavaScript exactly. They do not necessarily match the language you are going to paste the pattern into.
Named groups are (?<name>...) in JavaScript, .NET and modern PCRE, but Python also accepts (?P<name>...) and older tooling only understands the latter. Lookbehind is available in current JavaScript, .NET and PCRE, but must be fixed-width in Python and does not exist at all in Go. Go's RE2 and Rust's regex crate omit backreferences and lookarounds by design, which is what lets them guarantee linear time - a pattern that relies on either simply will not compile.
The meaning of the shorthand classes shifts too. In JavaScript \d is ASCII digits only, in .NET it matches digits in any script by default, and in Python it depends on whether the pattern is a str or bytes. And the u flag in JavaScript is what makes . and quantifiers operate on whole code points rather than code units, without which a pattern breaks on emoji and on any character outside the Basic Multilingual Plane.
Test against your own runtime before shipping a pattern that matters.
Writing a pattern you can still read later
Anchor it. A pattern without ^ and $ that is used to validate a field will happily match a substring, so an email pattern with no anchors accepts anything with a valid address buried inside it. This is a recurring source of validation bypasses.
Prefer character classes to alternation where both work: [abc] is clearer and faster than (a|b|c), and [^,]+ expresses will not cross a comma in a way that .+? only approximates. Name your groups rather than counting them, because (?<year>...) survives someone inserting a group in front of it and $3 does not.
Then test the negatives. Most patterns are written by trying inputs that should match until one does, which reliably produces something too permissive. The more useful exercise is to paste in the inputs that must not match and confirm they do not - the malformed address, the value with a delimiter inside it, the line that is nearly but not quite the log format you expect.
Reading a real pattern piece by piece
Take a pattern for a common log line: ^(\S+) \S+ \S+ \[([^\]]+)\] "(\w+) ([^"]*)" (\d{3}) (\d+)$. It looks dense and decomposes cleanly. The caret anchors at the start of the line. (\S+) captures a run of non-space characters - the client address - and the two unnamed \S+ that follow skip the identity and user fields nobody uses.
\[([^\]]+)\] captures the timestamp: an escaped bracket, then everything that is not a closing bracket, then the closing bracket. The negated class is what makes this robust, because it cannot run past the end of the field the way .+ would. The quoted request is the same idea with quotes, split into the method and the path.
(\d{3}) captures exactly three digits for the status code and (\d+) captures the byte count, with the dollar anchoring the end. The lesson generalises: a pattern built from negated classes and explicit counts reads more clearly and runs faster than the same pattern built from dots and lazy quantifiers.
Catastrophic backtracking and how to avoid it
Backtracking engines - which is most of them - try alternatives when a match fails. Nesting one quantifier inside another gives the engine an exponential number of ways to divide the input, so a pattern like (a+)+$ against a long string of a characters followed by a b can run for longer than the universe has existed.
This is a real denial-of-service vector, catalogued as ReDoS, and it has taken down production services: a regular expression in a request-parsing path, given a crafted input, pegs a CPU core indefinitely. Cloudflare's 2019 global outage was caused by exactly this shape of pattern.
Three defences cover most cases. Avoid nesting quantifiers and prefer a negated character class to a dot - [^"]* cannot overlap with what follows it, so there is nothing to backtrack. Use atomic groups or possessive quantifiers where the engine supports them. And where patterns come from users, run them in a worker with a timeout, which is what this tester does, or use a linear-time engine such as RE2.
Unicode, and why \w is not enough
The shorthand classes were defined for ASCII and largely still are. In JavaScript, \d matches only 0-9 and \w matches only ASCII letters, digits and underscore, so a pattern validating a name with \w+ rejects José, Zoë and every name not written in the Latin alphabet.
The Unicode property escapes solve this properly. With the u flag, \p{L} matches any letter in any script, \p{N} any numeric character, and \p{Script=Han} a specific writing system. Those are far more precise than widening a class by hand, and they keep working as Unicode adds characters.
There is a second reason to set the u flag: without it the engine works on UTF-16 code units rather than code points, so a dot matches half of an emoji and a character class can split a surrogate pair. Even with it, a visible character can be several code points - a flag, a family, an accented letter written as base plus combining mark - so counting characters with a regular expression still needs the v flag's set notation or a grapheme-aware library.
When not to use a regular expression
For fixed strings, ordinary string methods are clearer and faster: startsWith, includes, split. Reaching for a pattern to check whether a string ends in .json is a habit worth breaking, because the plain version needs no explanation later.
For anything with nesting - HTML, JSON, source code, balanced brackets - use a parser. A regular expression cannot count depth, so every pattern that appears to handle these is matching a common case and failing silently on the rest. The famous Stack Overflow answer about parsing HTML with regex is funny because the underlying point is exact.
For structured data there is usually a purpose-built tool: a CSV parser that understands quoted commas, a URL parser that understands userinfo and ports, a date library that understands time zones. Each of these exists because the regular-expression version was written thousands of times and was subtly wrong every time.
Patterns you will use repeatedly
Trimming and normalising whitespace: \s+ replaced with a single space collapses runs, and ^\s+|\s+$ trims when the language has no trim function. Extracting a quoted value: "([^"]*)". Splitting on a delimiter that does not appear inside fields: a negated class rather than a lazy dot.
Two patterns are worth not writing. Email validation by regular expression is a trap - the grammar in RFC 5322 is genuinely complex, and the pragmatic check is a non-empty local part, an at sign, and a dot in the domain, followed by sending a confirmation message, which is the only real validation. URL validation has the same shape of problem, and every language now ships a URL parser that answers the question properly.
Where a pattern is long enough to need explaining, use named groups and, in languages that support it, the extended flag that allows whitespace and comments inside the pattern. A pattern you can read in six months is worth two extra lines today.
Frequently asked questions
- What is catastrophic backtracking?
- Certain patterns take exponential time on inputs that nearly match. The classic shape is a quantifier inside another quantifier, such as (a+)+$ tested against a long run of a characters followed by something else: the engine tries every possible split before giving up. Seconds or minutes of frozen CPU is a normal outcome.
- Why does this tool not freeze when that happens?
- A running regular expression cannot be cancelled from JavaScript, so matching happens in a Web Worker instead. If it exceeds 1.5 seconds the worker is terminated and a fresh one takes its place. The page stays responsive and you get told what happened.
- What do the flags do?
- g finds every match rather than just the first, i ignores case, m makes ^ and $ match at line breaks, s lets . match a newline, u enables full Unicode handling including \p{...} property escapes, and y anchors matching at the current position.
- How do I reference capture groups when replacing?
- Use $1, $2 and so on for numbered groups, $<name> for named ones, $& for the whole match, and $$ for a literal dollar sign.
- Does this use the same engine as my code?
- It uses the JavaScript engine in your browser, so results match JavaScript exactly. PCRE, Python, Go and .NET differ in lookbehind support, named group syntax and Unicode handling, so verify against your own runtime before relying on a pattern.
- Is my test data sent anywhere?
- No. Both the pattern and the text stay in your browser, which makes this safe for logs and production samples.
Last updated:
References
MDN — JavaScript RegExpRecent changes
- — Added a runnable example, expected results, limitations, and reference links.
Include the steps, expected result, and actual result. Remove secrets and personal data.
