The regex pieces that cover most real jobs
Regular expressions have a reputation for being write-only, earned by the wrong half of the subject. The half that pays for itself is small: four ideas that between them cover most of the matching people actually do. Every output below is what the tester really printed.
The test text is four log lines:
2026-01-04 10:02 INFO order 1041 ok2026-01-04 10:03 WARN order 1042 retry2026-01-04 10:07 ERROR order 1043 failed2026-01-05 09:15 ERROR order 1044 failed
1. Character classes: which characters count
Square brackets mean “one character from this set”. [0-9] is one digit; [A-Za-z] is one letter. A class matches exactly one character, which is why it is nearly always followed by a quantifier.
2. Quantifiers: how many of them
+ means one or more, * means zero or more, ? means optional, and {4} means exactly four. Running [0-9]+ over the log returns twenty-four matches — 2026, 01, 04, 10, 02, 1041, and so on through every run of digits. Correct, and useless, which is the normal first result.
Quantifiers are greedy by default: they take as much as they can. Over the text <b>hi</b> and <i>there</i>, the pattern <.+> returns exactly one match — the entire string, <b>hi</b> and <i>there</i>. Adding ? after the quantifier makes it lazy, taking as little as it can, and <.+?> returns four matches: <b>, </b>, <i>, </i>. One character of difference, completely different answer. This is the commonest reason a pattern “nearly works”.
3. Anchors and boundaries: where it is allowed to match
^ is the start, $ is the end, and \b is a word boundary — the join between a word character and a non-word character. Anchors turn “contains something like this” into “is exactly this”.
The pattern [0-9]{3}-[0-9]{4} against call 555-0142 or ext 12345-6789 today returns two matches: 555-0142 and 345-6789. The second is a fragment yanked out of the middle of a longer number — exactly the bug that gets shipped. Anchor it — ^[0-9]{3}-[0-9]{4}$ — and against 555-0142 it matches, while against 555-0142 with one leading space it returns no matches. If you are validating a whole field rather than searching within text, anchor both ends.
The multiline flag m makes ^ and $ mean start and end of each line. With flags gm, the pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2} pulls the date off the front of every log line: 2026-01-04 three times and 2026-01-05 once.
4. Groups and alternation: this or that
Parentheses group things so a quantifier or an alternation applies to all of it, and | means or. (ERROR|WARN) over the log returns WARN, ERROR, ERROR — the severity levels, and nothing else.
One honest limit here: this tester lists whole matches, one per line. It does not break out capture groups. The pattern order ([0-9]+) returns order 1041, order 1042, order 1043, order 1044 — the full match each time, not the captured number. Groups still do their structural job; the captures are simply not shown.
The part that actually matters
Now the pattern that looks obviously right. \b[0-9]{4}\b reads as “a standalone four-digit number”, which is a fine description of an order id. Its real output over the log is eight matches: 2026, 1041, 2026, 1042, 2026, 1043, 2026, 1044. The year is also a standalone four-digit number. Nothing about the pattern is wrong; the assumption behind it was.
Emails do the same thing. \S+@\S+ over [email protected], [email protected]. see [email protected], returns [email protected],, [email protected]. and [email protected], — every one carrying the punctuation that followed it, which will fail the moment you compare two of them. Tighten it to [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} and the same text gives three clean addresses. Note \. in there: a bare . means any character, so a literal dot has to be escaped.
Neither of those problems is visible by reading the pattern. Both are obvious the instant you run it against text you recognise. So paste in the awkward rows on purpose — the blank one, the one with the trailing comma, the one in different capitals, the header row — and read every match. An elegant pattern that has never met your data is a guess; an ugly one that returns exactly the right rows is finished work.
The scope of this tester
It uses your browser's own regular-expression engine, so the flavour is JavaScript's and the flags behave as they do there — i for case-insensitive, m for multiline. The global flag is always applied, so you get every match rather than only the first. Output is the matched text, one per line, or the words no matches. There is no replace, no match count, no character positions and no capture-group column.
An invalid pattern gives you the engine's own message rather than a blank result: typing [0-9 returns Invalid regular expression: /[0-9/g: Unterminated character class. That is useful — an empty result you mistake for “no matches” is the worse failure.
Nothing is uploaded. The Regex Tester matches inside the page, so pasting a chunk of a real log or a column of real addresses into it does not send them anywhere. Which is the whole point, because testing on your actual data is the step that finds the year hiding among the order ids.