Regex Tester
Written without the surrounding slashes. The pattern is passed to the RegExp constructor, never evaluated as code.
gKeeps searching after the first hit instead of stopping there. Without it you get one match, however many exist.
2024-01-14at index 8- 1 · year
2024 - 2 · month
01 - 3 · day
14
- 1 · year
2024-02-03at index 30- 1 · year
2024 - 2 · month
02 - 3 · day
03
- 1 · year
2024-02-29at index 50- 1 · year
2024 - 2 · month
02 - 3 · day
29
- 1 · year
2024-03-11at index 77- 1 · year
2024 - 2 · month
03 - 3 · day
11
- 1 · year
What the match list tells you
A pattern either agrees with your text or it does not, and when the answer is “not” the useful question is which part disagreed. This tester lists every match with the character offset it starts at, so a pattern finding three of the four dates in a log line shows you which three — and the gap tells you what your character class forgot. It matches as you type, so the list is never a stale answer to an older pattern.
Flags, and the one that gets blamed for the wrong thing
Each flag is listed with what it changes rather than as a bare checkbox, because two of them are routinely confused. The m flag retargets ^ and $ at the start and end of every line; s is the one that lets a dot cross a newline. So /^\d+$/m against three lines of numbers matches each line separately, and adding s changes nothing — while /a.b/s will happily match an “a”, a line break and a “b”.
The rest earn their place too. Without g you get one match however many exist. The u flag treats the pattern as code points, so \u{1F600} is legal and an emoji counts as one unit rather than two; v is its stricter successor and cannot be combined with it, so ticking one here unticks the other.
Groups: numbered, named and absent
Capture groups are numbered by the position of their opening parenthesis, counting from the left, which is why adding one at the front silently renumbers everything after it. Naming them with (?<year>…) is the fix, and both the number and the name are shown here so a rewritten pattern can be checked against the code that consumes it.
A group can also match nothing at all, which is different from matching an empty string. Run /(a)|(b)/ against the text “b” and group 1 is reported here as not having participated, while group 2 holds “b”. In JavaScript that first value is undefined, and code that assumes a string is what crashes. Where a group exists only to carry a quantifier, (?:…) keeps it out of the numbering.
The pattern that can freeze a browser tab
Some patterns are not slow, they are effectively infinite. Given /(a+)+$/ and thirty a characters followed by a b, the engine must try every way of dividing those thirty characters between the inner and outer repetition before concluding there is no match — roughly a billion attempts, and each extra a doubles it. That is catastrophic backtracking, and as a denial-of-service vector it has its own name, ReDoS.
JavaScript cannot interrupt a running regex: there is no timeout argument and no way to cancel one, so once exec starts the only thing that ends it is the answer. This tool therefore checks the pattern first, and a repetition wrapped around a group that itself repeats or alternates is refused behind an explicit “run it anyway”. The check is deliberately cautious and will sometimes hold back a pattern that would have been fine — one extra click, against a dead tab and lost text.
Built on the RegExp constructor, never on eval
Your pattern is passed to new RegExp inside a try/catch. That matters because the obvious shortcut — evaluating /pattern/flags as a literal — would run whatever was pasted in as JavaScript. An unbalanced bracket or a bad quantifier instead comes back as the engine’s own SyntaxError, reported in place of the results.
The loop around it is bounded too: the first 20,000 characters are searched, at most 500 matches are listed, and a run over its time budget admits the list is incomplete. A zero-length match advances the cursor by one rather than looping forever, which is what /a*/g against “bbb” would otherwise do. Everything runs in your browser, so the text being tested never leaves the machine.
The g flag and catastrophic backtracking
Why does my pattern work here but not in my editor?
Because they are different flavours. This tester uses the JavaScript engine, while PCRE, Python and Go differ in the details: recursion, possessive quantifiers and \K exist in some and not others. Test against the engine that will actually run the pattern.
What does the g flag change apart from the number of matches?
It makes the regex object stateful. A global regex remembers a lastIndex between calls, so calling test() twice on the same string can return true and then false — a classic source of matches that vanish for no visible reason.
Why is a capture group reported as not participating?
Because that branch never ran — usually an alternation where the other side matched, or an optional group that was skipped. JavaScript gives you undefined there rather than an empty string, which is worth handling before it reaches a string method.
How do I stop a pattern backtracking catastrophically?
Make the repetition unambiguous, so there is only one way to divide the input. Replacing (\w+\s?)+ with \w+(?:\s\w+)* is the usual shape of the fix: the second form can match the same text in exactly one way.
What is the difference between .* and .*?
Greedy against lazy. .* takes as much as it can and hands characters back only when the rest of the pattern fails; .*? takes as little as it can and adds one at a time. Matching a tag with <.*> swallows the whole line, while <.*?> stops at the first closing bracket.
How do I match a literal dot or a backslash?
Escape it: \. matches a full stop rather than any character, and \\ matches a single backslash. Inside a character class most metacharacters are already literal, so [.] works as well.
Does \w match accented letters?
No. \w is exactly [A-Za-z0-9_], so é and ß fall outside it and a name filter built on \w quietly rejects people. With the u flag you can write \p{L} instead, which matches a letter in any script.
How do I match a line that does not contain a word?
With a negative lookahead anchored at the start: ^(?!.*forbidden).*$ matches any line where forbidden does not appear. A lookahead tests a position without consuming anything, which is why it can sit in front of .* and still leave the whole line to match.
Can I use a regular expression to parse HTML?
For a quick extraction from markup you control, yes; for anything nested or supplied by a user, no — a parser knows what a comment and an attribute are, and a pattern does not.
What is the offset shown beside each match?
The zero-based character index the match starts at, which is the same number JavaScript reports as match.index. A line break counts as one character, so it will not agree with an editor showing line and column.