About Regex Tester

Test regular expressions with live match highlighting and capture groups. Free, no sign-up required.

How to use

  1. Type a regex pattern into the Pattern field — bare expression, no surrounding slashes (the tool adds them internally). Example: \d{3}-\d{4} for North American 7-digit phone tails. Backslashes are written single, not doubled.
  2. Set the Flags field to control match behavior. Default is g (global, find every match). Add i for case-insensitive, m for multiline (^ and $ match line boundaries), s for dotall (. matches newlines), u for full Unicode, y for sticky positioning.
  3. Paste your test text into the Test String textarea. Matches highlight in real time as you type either field — there is no Run button, every keystroke re-runs the regex against the entire test string.
  4. The Results panel below the test string shows each match with its position offset and any capture groups. Numbered groups (group 1, group 2) come from parenthesized sub-patterns; named groups (?\d{4}) appear by name.
  5. If your pattern is invalid, the tool shows the JavaScript engine's error message verbatim ('Invalid regular expression: ...'). The most common errors are unbalanced parentheses, dangling quantifiers, and bad character class ranges.
  6. Remember that this tool runs the JavaScript regex engine — flavors differ. PCRE (PHP/Python re), .NET, Go's RE2, and Java each have their own quirks. A pattern that works here will work in JS/TypeScript, but may need adjustment for other languages.

Examples

Email-shaped match
Pattern: [\w.+-]+@[\w-]+\.[\w.-]+. Flags: gi. Tests against 'Contact alice@example.com or bob+spam@sub.example.co.uk'. Both addresses match — including the + alias and the multi-part TLD. Note: this is a pragmatic regex, not a full RFC 5322 validator (which is hundreds of characters).
Named capture for date parts
Pattern: (?\d{4})-(?\d{2})-(?\d{2}). Flags: g. Match against '2026-04-30 was today'. Result: full match with named groups year=2026, month=04, day=30. Access in code via match.groups.year. Named groups beat numbered when patterns get long.
Lazy versus greedy
Pattern .*? versus .* on text 'foobarbaz'. Greedy <.*> matches 'bar' (longest possible). Lazy <.*?> matches '' (shortest possible) and would match '' on a second iteration. Use lazy for tag-by-tag scanning; use greedy when you genuinely want the longest span.

Frequently asked questions

What do all the regex flags actually do?
g (global): find every match instead of just the first. i: case-insensitive (a matches A). m (multiline): ^ and $ match start/end of each line, not just the whole string. s (dotall): . matches newlines too (default behavior excludes \n). u (unicode): treat the pattern as Unicode-aware, enables \p{} property classes. y (sticky): match must start exactly at lastIndex; useful for tokenizers. d (indices): adds position info to match results. Combine flags by concatenating: gim, gsu, etc.
Why does my regex work in Python but not in JavaScript (or vice versa)?
Different engines, different rules. JS lacks PCRE's possessive quantifiers (a++) and atomic groups ((?>...)). Python's re lacks lookbehind length flexibility that PCRE has (and JS only added variable-length lookbehind in 2020). PCRE supports recursion ((?R)), JS does not. Character class shortcuts differ — \w matches [A-Za-z0-9_] in JS but is locale-aware in some engines. For maximum portability, stick to a common subset and test in the actual target engine.
What is the difference between greedy, lazy, and possessive quantifiers?
Greedy (the default: *, +, ?, {n,m}) matches as much as possible while still allowing the whole pattern to succeed — it backtracks if needed. Lazy (*?, +?, ??, {n,m}?) matches as little as possible, expanding only when forced. Possessive (*+, ++ — not in JS) is greedy but never backtracks, which prevents catastrophic backtracking but can cause matches to fail where greedy would succeed. JS has no possessive; emulate with atomic groups via (?=(...))\1 trick.
Why is my regex catastrophically slow on certain inputs?
Catastrophic backtracking. Patterns like (a+)+ on input 'aaaaab' force the engine to try every possible split — exponential in input length. Common culprits: nested quantifiers ((\w+)+), alternations with overlap ((a|a)*), and unbounded lookarounds. Fixes: use possessive quantifiers (in PCRE), use atomic groups, anchor with ^/$ to limit search space, or rewrite to avoid the ambiguity. Test with adversarial inputs (long strings of nearly-matching chars) to catch this in dev.
How do lookahead and lookbehind work, and what do browsers support?
Lookahead (?=...) asserts what follows without consuming it; (?!...) is negative. Lookbehind (?<=...) asserts what precedes; (?
What is the difference between numbered and named capture groups?
Numbered: (\d+) becomes group 1, (\w+) becomes group 2, etc. Access via match[1], match[2]. Named: (?\d+) is accessed via match.groups.digits. Named groups are self-documenting and survive pattern edits — adding a group at position 1 does not shift all your numbered references. Both can coexist; named groups also count in the numbered sequence. For complex patterns, always prefer named — your future self will thank you.
What does \b actually match?
Word boundary — a zero-width position between a word character (\w: [A-Za-z0-9_]) and a non-word character (or start/end of string). \bcat\b matches 'cat' in 'a cat sat' but not in 'category'. The dependency on \w means it does not respect Unicode by default; with the u flag and \p{Letter} classes you get smarter boundaries. \B is the negation — non-word-boundary, useful for matching mid-word patterns.

Part of ToolFluency’s library of free online tools for Developer Tools. No account needed, no data leaves your device.