Regular Expressions, Explained

After reading this you will be able to read a regular expression token by token, predict what it matches, and spot the patterns that freeze your browser tab.

What a regular expression is

A regular expression (regex) is a small program that describes a set of strings. You give the engine a pattern; the engine reports whether a piece of text matches, and if so, exactly which characters matched. The Regex Tester runs your pattern against sample text as you type, highlights every match, lists the capture groups, and breaks the pattern into plain English.

Here is a hook. The pattern \b\d{3}-\d{4}\b matches a phone fragment like 555-1234. Read it left to right: a word boundary, three digits, a literal hyphen, four digits, another word boundary. That is the whole idea. A regex is a sequence of tokens, each token matching one thing, read in order.

This tester uses JavaScript regex syntax. It covers almost everything PCRE offers, with two exceptions: possessive quantifiers (a++) and recursion. For 95% of day-to-day patterns the difference never comes up.

When to reach for regex, and when not to

Regex is the right tool when the structure you want is local and roughly linear: an email-shaped token, a date, a log line prefix, a hex color. It shines at validation, search-and-replace, and pulling fields out of semi-structured text.

It is the wrong tool for nested or recursive structure. HTML and JSON are the classic traps. HTML can nest tags to any depth, and a regular language cannot count depth. If you need to read JSON, use the JSON Formatter & Validator and query it with JSONPath, or convert it with the data converter. If you find yourself writing a pattern with five levels of optional groups to parse a config file, stop and use a real parser.

A good rule: if you can describe the target in one sentence without the word "nested", regex is likely fine. The moment you say "brackets inside brackets", switch tools.

The pieces and how they combine

Every regex is built from four kinds of token, combined in order.

Literals
Characters that match themselves. cat matches the three letters c, a, t in sequence.
Character classes
A set of allowed characters. [a-z] matches one lowercase letter. \d is shorthand for [0-9], \w for [A-Za-z0-9_], \s for whitespace.
Quantifiers
How many times the previous token repeats. ? is 0 or 1, * is 0 or more, + is 1 or more, {3} is exactly 3, {2,5} is 2 to 5.
Groups and anchors
Parentheses ( ) capture a sub-match; (?: ) groups without capturing. Anchors like ^ (start), $ (end), and \b (word boundary) match a position, not a character.

The engine matches greedily by default. A quantifier grabs as much as it can, then gives characters back only if the rest of the pattern fails. Add ? after a quantifier to make it lazy: a+? takes as little as possible. This single distinction explains most "why did it match too much?" confusion.

How matching actually runs

The JavaScript engine uses backtracking. At each choice point (a quantifier or an alternation |) it remembers where it was, tries one path, and if that path fails it returns to the remembered point and tries the next. Most of the time this is fast. The cost is bounded by the number of choice points multiplied by the input length.

T \approx n \cdot m

Here T is roughly the number of steps the engine takes, n is the length of the input text in characters, and m is the number of independent choice points in a simple pattern. For \d{3}-\d{4} against 100 characters, expect on the order of a few hundred steps. Nothing to worry about.

The danger is when choice points nest. If a quantifier contains another quantifier that can match the same characters, the number of paths grows exponentially. This is catastrophic backtracking.

T \approx 2^{n}

For the pattern (a+)+$ against a string of n letter a characters followed by one b, the engine explores about 2^{n} ways to split the run of a. At n = 25 that is over 33 million steps. At n = 35 it is 34 billion, and the tab freezes.

A pattern that backtracks catastrophically can freeze the tab. If your regex nests a quantifier inside a quantified group and the inner and outer parts can match the same characters, test it on short input first, then grow the input in steps.

Steps roughly double for each extra character. By 28 characters the engine is doing over 260 million steps for a failed match.

Reading a pattern token by token

Working through the demo pattern

The demo loads the pattern (\w+)@(\w+)\.(\w+) against the text Contact [email protected] or [email protected] today. Read it in order:

  1. (\w+) captures one or more word characters into group 1. Greedy, so it grabs as many as it can.
  2. @ matches a literal at-sign.
  3. (\w+) captures the next run of word characters into group 2.
  4. \. matches a literal dot. The backslash is required, because a bare . means "any character".
  5. (\w+) captures the final run into group 3.

Against the sample the tester reports two matches:

Matches and capture groups for the demo pattern
MatchFull textGroup 1Group 2Group 3
1[email protected]aliceexamplecom
2[email protected]bobtestorg

Notice what the pattern does not match. A real address like [email protected] would only capture b for group 1, because \w excludes the dot. This pattern is a teaching example, not a production email validator. The full email grammar in RFC 5322 is far larger than most people expect, and matching it exactly with one regex is rarely worth the effort.

Interpreting the results panel

Three outputs tell you what happened. The highlights show every match position in the text; overlapping highlights never occur, because the engine advances past each match. The capture list numbers groups from left to right by their opening parenthesis, so group 1 is always the first ( you wrote, even if it is nested inside another group. The plain-English breakdown restates each token so you can confirm the pattern says what you meant.

Two flags change everything. The global flag g makes the engine find all matches instead of stopping at the first; without it, the demo would highlight only [email protected]. The ignore-case flag i makes [a-z] also match uppercase. If your results show one match when you expected several, check whether g is on.

Greedy <.+> against <a><b> matches the whole string <a><b> in one go, because .+ grabs everything then backs up to the last >. Lazy <.+?> matches <a> and <b> separately, because .+? stops at the first >.

Common mistakes

Four errors account for most surprises.

  • Unescaped metacharacters. The characters . ^ $ * + ? ( ) [ ] { } | \ have special meaning. To match a literal dot, write \.. To match a literal backslash, write \\. The String Escaper escapes regex text for you.
  • Greedy overreach. ".*" on the line say "hi" and "bye" matches the whole span from the first quote to the last, capturing hi" and "bye. Use ".*?" to match each quoted part.
  • Anchors forgotten. Validating a whole string needs ^ and $. Without them, \d{4} "validates" the string abc1234xyz because it matches somewhere inside.
  • Dot does not cross lines by default. . matches any character except a newline. Add the s flag if you need it to span lines.

Related tools on this site

Regex often sits next to other text jobs. To clean up a query you extracted, use the SQL Formatter. To compare two structured documents instead of scanning text by eye, use the JSON Diff. To generate ignore rules rather than write path patterns by hand, use the .gitignore Generator.

Frequently asked questions

Why does my pattern match nothing when it looks right?

Check three things in order: an unescaped metacharacter (a bare . or +), a missing g flag when you expect several matches, and case sensitivity when you forgot the i flag. One of these explains most empty results.

What is the difference between a group and a capture group?

Both use parentheses. A capture group ( ) records what it matched, and appears in the result list numbered left to right. A non-capturing group (?: ) groups tokens for a quantifier without recording them. Use non-capturing groups when you only need the structure, not the value.

Why did my regex freeze the browser tab?

Almost always catastrophic backtracking. Look for a quantifier inside a quantified group where both can match the same characters, such as (a+)+ or (\w+\s*)+. Rewrite so each character has only one place it can match. Test on short input first.

Does this run on a server?

No. It runs 100% in your browser. Nothing you paste leaves your device, so you can test patterns against private log lines or sample data safely.

Is JavaScript regex the same as PCRE?

Nearly. JavaScript supports the same character classes, quantifiers, groups, lookahead, and lookbehind. It lacks possessive quantifiers (a++) and recursion. If a pattern from a PCRE tutorial fails here, one of those two features is the usual reason.