Common patterns
A practical catalogue of real-world regex patterns for emails, URLs, dates, phone numbers, and more — with honest caveats about what regex can and cannot do.
- Write and adapt working patterns for email, phone, date, and number formats
- Explain why "good enough" patterns are often better than RFC-compliant ones
- Use regex101.com as a debugging and exploration tool
The most useful regex skill is not memorizing patterns — it is knowing the shape of a problem well enough to compose a pattern quickly and verify it. This lesson catalogues the patterns you will reach for most often, explains the tradeoffs in each, and teaches you to adapt them.
The "good enough" principle
A fully RFC-compliant email regex runs to several hundred characters. Nobody reads it, few people can debug it, and it still rejects some theoretically valid addresses. For almost every real-world use case, a shorter pattern that handles 99 % of inputs correctly is far better.
Write the simplest pattern that correctly handles your actual data. If an edge case arises, add exactly what handles that case. Do not start from the most permissive possible interpretation of a specification.
Email addresses
A pragmatic email pattern:
/^[\w.+-]+@[\w-]+\.([\w-]+\.)*[a-z]{2,}$/iBreaking it down:
| Part | Meaning |
|---|---|
^[\w.+-]+ | Local part: word chars, dots, plus, hyphen |
@ | Literal at-sign |
[\w-]+ | Domain name label |
\. | Literal dot |
([\w-]+\.)* | Optional subdomains, each ending with a dot |
[a-z]{2,}$ | TLD: at least 2 letters |
const emailRe = /^[\w.+-]+@[\w-]+\.([\w-]+\.)*[a-z]{2,}$/i;
emailRe.test("user@example.com"); // true
emailRe.test("user.name+tag@sub.co.uk"); // true
emailRe.test("@example.com"); // false — no local part
emailRe.test("user@"); // false — no domainFor form validation, HTML's type="email" input already applies a reasonable
email check. Use regex for server-side validation or when working with text that
isn't in a form field.
Phone numbers
Phone formats vary enormously by country. A pattern that matches common US formats:
/^(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/This handles:
555-123-4567(555) 123-4567555.123.4567+1 555 123 4567
const phoneRe = /^(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
phoneRe.test("555-123-4567"); // true
phoneRe.test("(555) 123-4567"); // true
phoneRe.test("5551234567"); // true
phoneRe.test("55-1234"); // falseFor international numbers, consider a library like libphonenumber rather than
regex — the format space is too large for a simple pattern to handle reliably.
Dates
An ISO date (YYYY-MM-DD):
/^\d{4}-\d{2}-\d{2}$/Note that this matches "9999-99-99" — regex cannot validate whether the date is
real (February 30 would pass). For business logic, parse to a Date object after
the structural match passes.
Common US date MM/DD/YYYY:
/^\d{1,2}\/\d{1,2}\/\d{4}$/A flexible extractor that finds dates in text:
const text = "Shipped 2024-03-15, expected 2024-03-22";
const dates = text.match(/\d{4}-\d{2}-\d{2}/g);
// ["2024-03-15", "2024-03-22"]Integers and decimal numbers
// Match an integer (optional leading minus)
/-?\d+/
// Match a decimal (requires at least one digit on each side of the dot)
/-?\d+\.\d+/
// Match either integer or decimal
/-?\d+(\.\d+)?/
// Match numbers in scientific notation
/-?\d+(\.\d+)?([eE][+-]?\d+)?/"The value is -3.14".match(/-?\d+(\.\d+)?/); // ["-3.14", ".14"]
"Gain: +2.5%".match(/[+-]?\d+(\.\d+)?/); // ["+2.5", ".5"]The .14 in the first result is the captured group, not a separate match — the
full match is "-3.14". JavaScript's .match() without g returns the full
match at index 0 and captured groups at subsequent indices.
IP addresses
A simplified IPv4 pattern:
/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/This matches the structural form but allows 999.999.999.999. To validate ranges
(0–255) requires either a more complex pattern or parsing each octet after the
structural match:
function isIPv4(s) {
if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(s)) return false;
return s.split(".").every(n => Number(n) <= 255);
}
isIPv4("192.168.1.1"); // true
isIPv4("192.168.1.999"); // false
isIPv4("192.168.1"); // falseThis pattern — regex for structure, code for value constraints — is the right architecture for "mostly regular" data.
URLs
A practical URL extractor (not a validator):
/https?:\/\/[\w-]+(\.[\w-]+)+([\/\w\-.?=&%#]*)?/gconst text = "Visit https://example.com/path?q=1 or http://other.org";
const urls = text.match(/https?:\/\/[\w-]+(\.[\w-]+)+([\/\w\-.?=&%#]*)?/g);
// ["https://example.com/path?q=1", "http://other.org"]For strict URL validation, the browser's URL constructor is more reliable than
regex:
function isValidUrl(s) {
try { new URL(s); return true; }
catch { return false; }
}Whitespace and empty lines
Strip leading and trailing whitespace:
" hello world ".replace(/^\s+|\s+$/g, ""); // "hello world"
// Or just use .trim() — built-in and clearerFind empty or whitespace-only lines:
const emptyLine = /^\s*$/m;Collapse multiple spaces into one:
"too many spaces".replace(/\s{2,}/g, " "); // "too many spaces"Hex colours
// CSS hex colour (#rgb or #rrggbb)
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i/^#([a-f0-9]{3}|[a-f0-9]{6})$/i.test("#ff0000"); // true
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i.test("#f00"); // true
/^#([a-f0-9]{3}|[a-f0-9]{6})$/i.test("#gg0000"); // falseThe tool you should have open
regex101.com is the single best companion tool for writing regex. It:
- Highlights each match in real time as you type
- Explains every part of your pattern in a sidebar
- Shows the engine's step-by-step evaluation (invaluable for debugging)
- Supports JavaScript, Python, PHP, Go, and other flavours
Whenever a pattern is not matching what you expect, paste it into regex101 and look at the explanation — the engine's reasoning is usually immediately obvious.
Where to go next
You are ready for the Practical matching lab — apply these patterns and all the beginner syntax to a realistic block of text. After that, the intermediate tier opens up: capturing groups, named groups, backreferences, and lookarounds.