Read the pattern, not just the result
A regex tester earns its keep in the moment the pattern almost works. This one shows every match with the position it starts at, what each numbered and named group captured, a preview of a replacement, and — the part most testers leave out — the expression itself broken into tokens with a sentence on what each one does. If you inherited a pattern from a colleague or a search result, that last table is usually the fastest route to understanding it.
The engine is your own browser's. There is no server evaluating anything, which means the behaviour you see is exactly the behaviour you will get in Node or in a script running in a page.
JavaScript is a dialect, not the standard
There is no single regular expression language, and the differences bite in production rather than in a tester. JavaScript has no free-spacing mode, so you cannot break a long pattern across lines with comments the way Perl, Python and PCRE allow. It has no possessive quantifiers and no atomic groups, which are the two tools other engines give you for stopping runaway backtracking. Lookbehind exists and is unrestricted, but only in reasonably recent runtimes.
In the other direction, the u and v flags give you Unicode property escapes that older engines
lack, and named groups use pointed brackets where Python uses a different spelling entirely. Copying
a pattern between languages is not a copy; it is a translation, and it deserves a test on both
sides.
Greedy, lazy, and the difference in one example
Quantifiers take as much as they can and then give characters back until the rest of the pattern
fits. Against the text <b>one</b><b>two</b>, the pattern <b>.*</b> matches the entire line,
because the star first swallows everything and then retreats only far enough to find a final closing
tag. Adding a question mark makes it lazy, so <b>.*?</b> stops at the first closing tag and finds
two separate matches.
Neither behaviour is wrong. Greedy is right when you want the outermost pair of delimiters; lazy is right when you want each inner pair. Most bug reports about a pattern matching "too much" are this distinction and nothing else. Turning the flags on and off in the panel above, with the token table open, is a quick way to feel where the boundary sits.
Catastrophic backtracking, and why the input is capped
Some patterns have a cost that explodes. The classic shape is a repeated group that itself repeats,
like (a+)+$ run against a long run of the letter a followed by anything else. Each way of dividing
those letters between the inner and outer repetition is a distinct path, the number of paths doubles
with every extra character, and the engine tries them all before admitting defeat. Twenty-five
characters can be milliseconds; thirty-five can be minutes.
Real patterns hit this too, usually where two alternatives can match the same text, and a validator for email addresses or nested quotes is where it shows up most often. The defences are to avoid overlap between alternatives, to prefer a negated character class over a dot, and to test with input longer than the examples you developed against.
Because the expression here runs on the page with nothing able to interrupt it, the pattern is limited to 500 characters and the text to 20,000. That keeps the worst case finite.
Working with the text itself rather than a pattern? The word counter measures it, sort lines reorders it, and remove duplicate lines thins it out.