A regex is a mini-language for describing text shapes, not a single feature

Many people treat a regular expression as "one button in a search box", but the more useful mental model is: it is a small language of special characters that describes what a piece of text looks like. For example, \d{4}-\d{2}-\d{2} describes the shape "four digits, hyphen, two digits, hyphen, two digits" — not a specific date — and ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ describes the structure "local-part@domain". Because it is a shape description rather than a fixed value, the same pattern is reusable across email, logs, IDs and URLs. To use it well you only need to understand three things: how characters are written (classes, escaping), how repetition is written (quantifiers, greedy vs lazy) and how structure is grouped and anchored (groups, assertions, ^ $). This tool builds and runs the pattern locally in your browser's ECMAScript engine; the tables below help you avoid the most common traps.

Regex engine families: why the same pattern behaves differently elsewhere

The same regex can "run / throw / give different results" in a browser, PHP, Java or Python because the underlying engine differs. Mainstream engines fall into two camps: backtracking NFA (the default for most languages) and DFA / automaton (RE2 and friends: linear-time, never backtracks). The table contrasts the runtimes developers meet most often, based on the ECMA-262 15th edition (ES2024), PCRE2, Python re and Java java.util.regex documentation.

Engine / implementationTypical useBacktracking behaviourKey syntax differences
ECMAScript (browser / Node.js, what this tool uses)Front-end validation, JS scriptsBacktracking NFA; greedy by default, lazy *? supported, possessive quantifiers unsupported\p{...} needs the u flag; named groups (?<name>...); under u, \d matches all Unicode digits
PCRE2 (PHP, Notepad++, Apache, etc.)Server side, editorsBacktracking; possessive *+ and atomic groups (>...) mitigate ReDoSRichest syntax; \p{L}, backtracking control (*SKIP)
RE2 (Go regexp, some Rust crates)High-concurrency serversDFA / automaton, linear time, never backtracks, inherently ReDoS-safeNo backreferences, no lookbehind; a syntax subset
Python re (CPython default)Data cleaning, scrapingBacktracking; no possessive quantifiers; atomic grouping as an experimental feature since 3.11\p needs the third-party regex library; . does not match newline by default
Java java.util.regexEnterprise back endsBacktracking; possessive *+ and atomic groups (>...) supported\p{L}, named groups (?<name>...); matches() anchors the whole string by default

Note: this tool runs on the browser's ECMAScript engine, so a possessive quantifier like *+ throws a syntax error here. If your pattern comes from Java or PCRE and uses *+, rewrite it or drop the possessive modifier. The difference from server-side PCRE/Java is the most common source of integration surprises.

Backtracking vs DFA: how one bad pattern can take down a service (measured)

A backtracking engine "guesses": the + quantifier first eats as much as possible, and when the rest fails to fit it "spits back" and re-partitions. Usually that is fast, but certain nested quantifiers — such as (a+)+$ — make the number of re-partitions grow exponentially with input length. That is ReDoS (Regular Expression Denial of Service). In web contexts, an attacker only needs to send a crafted long string to peg the validation endpoint's CPU. OWASP (Top 10:2021, A06) lists ReDoS as a classic web risk. The table below is measured on this machine (Node v22 / 12th-gen Intel i5-12600KF; input is n copies of a followed by one !, for relative comparison only):

Input length (n a's + one !)(a+)+$ time (ms)Linear a+ time (ms)Note
182.22762Both fast at short length
208.49734Catastrophic backtracking begins
2231.90769Roughly x4 per +2 length
24128.24788Already 6x the linear version
26517.60818Half a second; noticeable lag
282316.20786Over 2s; endpoint effectively down
3015446.63824About 15s; service taken down

The contrast is clear: the linear regex a+ stays roughly flat (~780ms, first-compile noise) while the catastrophic version climbs from 2ms at 18 letters to 15 seconds at 30 — about 7000x. Rule of thumb: avoid "quantifier inside quantifier" where both can match the same character (e.g. (a+)+, (a*)*, (.*a){10}). Validate user input with simple, deterministic patterns on both client and server, and prefer a linear engine like RE2 when you can. This tool highlights matches in real time but does not block ReDoS-prone patterns for you — stress-test complex regexes with longer input yourself.

Metacharacters & quantifiers cheat sheet

This table covers about 90% of daily usage. Key point: in ECMAScript there is only greedy and lazy, never possessive; to get the anti-backtracking effect of "possessive" you must restructure (e.g. use [^x]+ instead of .*).

SymbolMeaningGreedy / lazy / possessiveNote
*Previous item, 0 or more timesgreedy * / lazy *? / possessive *+ (ECMAScript unsupported)Most common ReDoS source
+Previous item, 1 or more timesgreedy + / lazy +? / possessive ++ (ECMAScript unsupported)At least one
?Previous item, 0 or 1 timegreedy ? / lazy ?? / possessive ?+Also makes a group non-capturing (?:...)
{n,m}Previous item, n to m timesgreedy {n,m} / lazy {n,m}? / possessive {n,m}+{3,} at least 3, {3} exactly 3
\bWord boundary (zero-width)—Matches a position, not a character
\dDigit—Without u, only [0-9]; with u, all Unicode digits
\wWord character—[A-Za-z0-9_]; not Chinese even under u
\p{L}Unicode letter property—Needs u flag; \p{Script=Han} matches Han characters

Flags reference

Flags decide how to search, not what. This tool exposes g / i / m / s / u; the ECMAScript y (sticky) flag is listed for completeness:

FlagEffectTool default
gGlobal: find all matches, not just the firstOn by default
iIgnore case: a also matches AOff by default
mMultiline: ^ $ anchor each line's start/endOff by default
sdotAll: let . also match newlineOff by default
uUnicode code-point mode: enables \p{...}, correct surrogate handlingOff by default (turn on for non-Latin text)
ySticky: match only at lastIndex; common in front-end codeNot a UI toggle; write it inline if needed

Common pattern library: patterns and known limits

These are high-frequency patterns pulled from real projects, all compile-verified on this machine. But regex validation is "shape validation", not "semantic validation" — each row also notes what it fails to catch, which is exactly where such tools are most often misused.

PurposePatternKnown limits
Email^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$No quoted local parts, no IDN domains, no consecutive dots; passes weak addresses like a@b.c
URL (http/https)^https?:\/\/[\w.-]+(?:\/[\w./?%&=#-]*)?$No port check, no ftp, no TLD validation; shape only
Date YYYY-MM-DD^\d{4}-\d{2}-\d{2}$Passes invalid calendar values like 2026-13-45; needs a separate date sanity check
IPv4 address^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$Allows leading zeros like 192.168.001.1; does not reject reserved blocks like 0.0.0.0
International phone (E.164)^\+[1-9]\d{1,14}$Only checks the +CountryCode format; does not verify the number is allocated or in service
Password strength^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{8,}$Only checks "lower + upper + digit + symbol + 8 chars"; passes trivial passwords like Password1!. Pair it with a password generator and the password security guide

Three real examples (using the page default input)

All three examples use this tool's default test text 2026-08-23 released a new version, 2026-09-01 update planned. (only the g flag checked by default); paste them straight in to reproduce. To double-check how many characters or words a text holds, the word counter helps; to compare two revisions of a text, use the text diff tool.

Example 1: count all dates

Pattern \d{4}-\d{2}-\d{2}, flag g. On the default text this yields 2 matches: 2026-08-23 (position 0) and 2026-09-01 (position 35). This is exactly the placeholder example — the most direct way to "pull out every date".

Example 2: split year / month / day into capture groups

Pattern (\d{4})-(\d{2})-(\d{2}), flag g. Still 2 matches, but each now has 3 capture groups: group 1 year (2026), group 2 month (08 / 09), group 3 day (23 / 01). The tool's "Capture Groups" list shows them so you can extract by field.

Example 3: extract every number

Pattern \d+, flag g. The default text has 6 numeric matches: 2026@0, 08@5, 23@8, 2026@35, 09@40, 01@43. Notice \d+ treats "2026" as one number, not four single digits — that is the quantifier "eating greedily".

Common regex pitfalls

① Escaping. . * + ? ( ) are special; to match them literally add a backslash (\. \*). In a programming-language string the backslash itself often needs escaping too — the #1 reason "it looks right but throws".
② Greedy vs lazy. Quantifiers are greedy (match as much as possible) by default; add ? for lazy (.*?). Getting this wrong when extracting HTML tags can swallow half an article in one match.
③ Forgetting anchors. To validate "the whole string is an email", wrap it in ^ and $; otherwise it matches only a substring and the validation "leaks".

Frequently asked questions

Why does the same regex run here but error or differ in Python / Java? Different engine dialects (see the engine-family table above). The sharpest gaps: ECMAScript has no possessive quantifier *+ while Java/PCRE do; PCRE's (*SKIP) backtracking control has no JS equivalent; Python re does not match newline with . by default, unlike JS with s. Check the table for syntax portability before moving a pattern across languages.

Why does \d match a different range with and without the u flag? Without u, \d is only [0-9] (ASCII digits). With the u flag, ECMAScript treats it as Unicode, so \d also matches Arabic, full-width and other scripts' digits. If your text may contain full-width digits like "2026", turn on u or the validation misses them.

How do I validate an email without a regex that hangs? Email/URL checks are usually safe on their own; the risk is "nested quantifier + long input". Rule: use the flat patterns in the library above, never something like ([\w]+@[\w]+)+; truncate over-long user input before validating, or use a linear engine like RE2. More on breach and weak-password defence in the password security guide.

Do many capture groups hurt performance? Is a named group (?<name>...) different from an anonymous one? They are functionally equal; named groups just make output readable and stop later code breaking when a group is inserted and shifts the indices. The performance cost is tiny; what actually bites is "nested capture groups + catastrophic backtracking", independent of whether groups are named.

Why does my browser freeze on a complex regex over a large text? Almost certainly ReDoS (see the measured table). The browser is also a backtracking engine, and (a+)+ over long input explodes exponentially. To diagnose: stress-test with longer input and watch whether time climbs steeply with length; if so, replace nested quantifiers with character-class boundaries (e.g. [^x]+) or split into several simple passes.