Paste a regular expression and a test string to check matches quickly and verify your REGEX behaves as expected.
g (global — find all matches, not just the first), i (case-insensitive), m (multiline — make ^ and $ match line boundaries), s (dotAll — make . match newlines), u (unicode), and y (sticky — match only from lastIndex). Or pick a preset from the dropdown (Email, URL, Phone (US), IP Address, Date YYYY-MM-DD) to start from a known-good pattern.Regex is one of the few tools that's identical-looking and behaviorally different across engines — a pattern that works in grep -P can fail in Python's re, and a JavaScript regex with (?<=...) will silently crash in Safari 16.3 even though it runs fine in Chrome. A live tester compresses the write-run-fix loop from minutes to milliseconds and surfaces engine quirks before they ship.
(?:...) and \b before changing anything(a+)+b against aaaaaaaaaaaaaaaaX will hang the browser tab; spotting that locally is cheaper than a ReDoS outage (Cloudflare's July 2019 30-min global outage was caused by one regex)sed -i across a repoDifferent engines support different features. The same pattern can match in one and throw in another. Per Wikipedia's Comparison of regular expression engines and standards docs:
| Feature | JavaScript (ES2018+) | Python re |
Python regex (PyPI) |
PCRE2 | Go RE2 | .NET | Java |
|---|---|---|---|---|---|---|---|
Named groups (?<name>...) |
Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Fixed-length lookbehind | Yes | Yes | Yes | Yes | No | Yes | Yes |
| Variable-length lookbehind | Yes | Yes | Yes | No | No | Yes | No |
Atomic groups (?>...) |
No | No | Yes | Yes | No | Yes | Yes |
Possessive quantifiers *+ ++ |
No | No | Yes | Yes | No | No | Yes |
Recursion (?R) / subroutines |
No | No | Yes | Yes | No | No | No |
Backreferences \1 |
Yes | Yes | Yes | Yes | No | Yes | Yes |
Unicode property \p{L} |
Yes (u flag) |
Yes | Yes | Yes | Partial | Partial | Partial |
| Catastrophic backtracking risk | Yes | Yes | Yes | Yes | No (linear time) | Yes | Yes |
Go's regexp package uses RE2 (Thompson NFA) and guarantees O(n) matching — that's why it omits backreferences, lookarounds, and recursion. If you want guaranteed safety against ReDoS, Go's engine or Rust's regex crate (also RE2-style) are the only options on this list.
These are the patterns developers reach for daily. They're "good enough for 99% of real input" — not RFC-perfect. Trade-offs noted.
| Use | Pattern | Notes |
|---|---|---|
| Email (practical) | ^[\w.+-]+@[\w-]+(\.[\w-]+)+$ |
Rejects quoted local parts and IP-literal domains, but matches ~99% of real-world emails. Don't try to encode all of RFC 5322 — the canonical "full" regex is 6 KB and still wrong |
| URL (HTTP/HTTPS) | ^https?:\/\/[^\s/$.?#].[^\s]*$ |
Quick sanity check, not a parser. Use URL.canParse() in JS or urllib.parse in Python for real validation |
| IPv4 | `^((25[0-5] | 2[0-4]\d |
| ISO 8601 date | ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ |
Month 01-12 and day 01-31. Doesn't reject Feb 30 — calendar validation needs code |
| US phone | ^\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})$ |
Accepts (555) 123-4567, 555-123-4567, 5551234567. For international, use libphonenumber |
| UUID v1-v5 | ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ |
Case-insensitive (i flag). For UUID v7 (time-ordered), change [1-5] to [1-7] |
| Hex color | ^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$ |
3, 6, or 8 hex digits — covers #fff, #ffffff, and #ffffff00 (with alpha) |
| Strong password | ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{12,}$ |
Uses lookaheads to require mixed case, digit, symbol, and length 12+ |
Need to test something more exotic? Build patterns for JSON, JWT, or cron expressions in tools that natively understand the format, then come back here for the final pattern check.
(?<=...) lookbehind work in my regex?Almost certainly an engine or browser version issue. Per caniuse, JavaScript lookbehind landed in Chrome 62 (2017), Edge 79, Firefox 78, and Safari 16.4 (March 2023) — so any iPhone on iOS 16.3 or earlier throws a SyntaxError on parse. Node.js has had it since v10. Other engines: Python's stdlib re requires fixed-length lookbehind; the third-party regex package on PyPI allows variable-length. PCRE2 requires fixed-length. Go's RE2 doesn't support lookbehind at all. Global usage is around 94%, but if you ship to older Safari, polyfill or rewrite without lookbehind.
It's when a regex engine tries an exponential number of paths before giving up. The classic example is ^(a+)+b$ against "aaaaaaaaaaaaaaaaaaaaaaaaX" — the engine tries every way to split the as between the inner and outer groups before realizing there's no b. Each added a doubles the work. Real outage examples: Stack Overflow's 34-minute downtime in July 2016 was traced to a \s* matching trailing whitespace, and Cloudflare's July 2019 global outage came from a regex with nested quantifiers in their WAF. Fixes: (1) avoid nested quantifiers on the same character class — rewrite (a+)+ as a+; (2) use atomic groups (?>a+)+ where supported (not in JS or Python re); (3) use possessive quantifiers a++ (PCRE/Java); (4) switch to a linear-time engine like Go's RE2 or Rust's regex for adversarial input. This tester aborts patterns that run too long and warns you.
Greedy (*, +, ?, {n,m}) match as much as possible, then backtrack. Lazy (*?, +?, ??, {n,m}?) match as little as possible, then expand. The difference bites you on patterns with overlapping match boundaries. Example: against <b>bold</b><i>italic</i>, the pattern <.*> (greedy) matches the entire string, while <.*?> (lazy) matches just <b>. For HTML/XML scraping, lazy is almost always what you want — but really, use a parser for HTML, not regex. The tester's highlight bars make the difference immediately visible: toggle the ? and watch the match shrink.
Probably because you copied the RFC 5322 "official" regex from a Stack Overflow answer and it doesn't actually implement RFC 5322 — it implements a 2002-era subset and rejects perfectly valid plus-addressing ([email protected]), apostrophes (o'[email protected]), and Unicode local parts. The realistic approach is a simple pattern like ^[\w.+-]+@[\w-]+(\.[\w-]+)+$ for format sanity, then verify deliverability with a real validation API or send a confirmation email. The full RFC 5322 grammar is roughly 6 KB of regex and still doesn't catch every edge case — every "perfect email regex" article is wrong. If you absolutely need RFC compliance, use a parser library (Python's email.utils.parseaddr, JS email-addresses package, .NET MailAddress), not a regex.
[:alpha:] work here?Not in the JavaScript engine this tester uses. POSIX classes ([[:alpha:]], [[:digit:]], [[:space:]]) are supported in PCRE, Python re, Java, .NET, and most Unix tools (grep -E, awk, sed) but were never added to ECMAScript. The JS equivalents are \w, \d, \s plus Unicode property escapes with the u flag: \p{L} for any letter, \p{N} for any number, \p{Z} for separators. \p{L} is closer to [[:alpha:]] than \w is — \w only matches ASCII [A-Za-z0-9_] while \p{L} matches letters from every script.
Both, in that order. A browser tester catches 95% of syntax errors and logic bugs instantly, but the final pattern must run against your real engine — PCRE2 differs from JavaScript on lookbehind, recursion, and possessive quantifiers; .NET supports balancing groups that no one else does; Java's Matcher.find() vs Matcher.matches() distinction trips people up. After this tester says your pattern is good, paste it into your project's test suite with at least 5 inputs: a happy path, an empty string, a string with regex metacharacters, a Unicode string, and a deliberately adversarial string (10× the expected length). If the test suite passes, ship it.
\b and \B?\b matches a zero-width word boundary — the position between a \w (word char: [A-Za-z0-9_]) and a non-\w. \B matches a non-boundary. So \bcat\b matches cat in "the cat sat" but not in "cathedral". Common gotcha: \b is ASCII-only in JavaScript even with the u flag — \bcafé\b won't match in "the café opened" the way you'd expect because é is a \w char under Unicode but the boundary check doesn't follow. For Unicode word boundaries, use (?<=^|\P{L})cat(?=\P{L}|$) with the u flag.
Patterns and test strings live in your browser session — nothing persists when you close the tab and nothing is sent to a server. To share, copy the pattern and a few sample inputs into a message or commit them into your repo's test fixtures (best practice — your regex tests live in your codebase, not in a third-party site). If you need a permanent URL with shareable state, regex101 and regexr both offer that at the cost of uploading your pattern and test data to their servers. For sensitive content (production log excerpts, customer data, security payloads) stay local.
The JavaScript engine handles inputs up to a few MB without trouble, though real-time highlighting on every keystroke slows down past ~500 KB. If you're testing against a 100 MB log file, sample it down to a representative few hundred lines — that's enough to validate the pattern, and anything bigger is what a grep or ripgrep command on your actual machine is for. For text-comparison tasks beyond regex matching, text diff handles arbitrary-size string comparison in the browser.