Regex is one of those skills that pays dividends across every language you’ll ever write. The same pattern that validates an email in Python strips query strings in Go and sanitizes form input in JavaScript. Learn it once, use it everywhere.
This isn’t a syntax primer. It’s the patterns you actually reach for in real projects — the ones that show up in form validators, log parsers, URL routers, and data pipelines. The table below is the core of it. The prose around it covers where people get burned.
The Patterns Worth Keeping
These 11 patterns cover the scenarios that come up repeatedly in production code. Use the Regex Tester to validate them against your own inputs before wiring them in.
| Pattern | What It Matches | Example Match | Notes |
|---|---|---|---|
^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$ | Email address | user@example.com | Permissive by design — true RFC 5322 is a rabbit hole. Anchor with ^$. |
https?:\/\/[\w\-._~:/?#[\]@!$&'()*+,;=%]+ | HTTP/HTTPS URL | https://example.com/path?q=1 | Doesn’t validate structure — just confirms it looks like a URL. |
^(\d{1,3}\.){3}\d{1,3}$ | IPv4 address (format only) | 192.168.1.1 | Matches 999.999.999.999 — validate range in code, not regex. |
^(\+\d{1,3}[\s-])?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$ | North American phone number | (555) 867-5309 | Handles common delimiters. International formats vary too much for one pattern. |
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} | UUID v4 | 550e8400-e29b-41d4-a716-446655440000 | Case-insensitive — use i flag or add A-F to character classes. |
^[a-z0-9]+(?:-[a-z0-9]+)*$ | URL slug | my-article-title | No leading/trailing hyphens, no consecutive hyphens. |
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ | ISO 8601 date (YYYY-MM-DD) | 2026-04-10 | Validates format and range, not calendar validity (Feb 31 passes). |
^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ | CSS hex color | #ff6600 or #f60 | Both 3- and 6-digit shorthand. Add {8} for 8-digit RGBA hex. |
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\w.-]+))?(?:\+([\w.-]+))?$ | Semantic version string | 1.2.3-beta.1+build.42 | Captures pre-release and build metadata as optional groups. |
\b(?:\d{4}[\s-]?){3}\d{4}\b | Credit card number (mask, not validate) | 4111 1111 1111 1111 | Use for masking in logs, not validation. Luhn check requires code. |
\s+ | Whitespace runs | "hello world" → "hello world" | Replace with a single space for whitespace normalization. |
Where Patterns Break in Practice
Greedy vs. Lazy Matching
Quantifiers like * and + are greedy by default — they consume as much as possible. This bites you when extracting content between delimiters. <.+> on <b>bold</b> matches the entire string, not <b>. Switch to lazy with .+? to stop at the first closing delimiter instead of the last.
Anchoring: When ^ and $ Aren’t Enough
Without anchors, a pattern can match anywhere in a string. \d+ matches abc123def — it finds the digits in the middle. For full-string validation, wrap patterns in ^...$. In multiline mode (m flag), ^ and $ match line boundaries, not the full string boundaries. If you’re validating a single value (an email, a UUID), set the m flag intentionally, not by habit.
The Multiline Flag Pitfall
The m flag changes what ^ and $ mean. The s (dotAll) flag changes what . matches — without it, . doesn’t match newline characters. Parsing multi-line log entries with .+? You need s or [\s\S]+ as a fallback for engines that don’t support dotAll.
Testing Before Wiring In
Don’t drop a regex into an application and test it against one happy-path input. Patterns fail on edge cases: unicode characters, empty strings, leading/trailing whitespace, inputs that are almost-but-not-quite valid. The IO Tools Regex Tester lets you run a pattern against multiple test strings simultaneously, so you can check the valid cases and the invalid ones side by side before the code ships. The Regex Cheatsheet tool is useful when you’re in the middle of building a pattern and need a quick syntax reference without leaving your browser.
When testing, build a matrix: what should match, what definitely should not, and what’s on the boundary. An email validator that accepts user@ or rejects user+tag@example.co.uk is worse than no validator at all.
Language Quirks Worth Knowing
The patterns in the table above are broadly portable, but the engine matters at the edges.
- JavaScript uses the ECMA regex engine. Named capture groups (
(?<name>...)) are supported in ES2018+. Thevflag (introduced in ES2024) adds set notation and Unicode property escapes. Lookaheads work; lookbehinds are ES2018+ and have variable-length restrictions in some older V8 versions. - Python uses the
remodule (PCRE-adjacent but not identical). There.compile()pattern is worth using — compiled patterns are faster when called repeatedly in loops. One subtle difference: Python’s\bword boundary is Unicode-aware by default, so it works on non-ASCII word characters. JavaScript’s\bis ASCII-only unless you use theuorvflag. - Go uses RE2 syntax, which deliberately excludes backreferences and lookaheads. This is a security decision — RE2 guarantees linear-time matching, which prevents catastrophic backtracking. If you’re porting a pattern from Python or JS to Go and it uses lookaheads or backreferences, you’ll need to restructure the logic.
Real-World Example: Email Validation Across Languages
Same pattern, two implementations — a JS form validator and a Python input check:
// JavaScript — client-side form validation
const EMAIL_RE = /^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$/;
function validateEmail(input) {
const value = input.trim();
if (!EMAIL_RE.test(value)) {
throw new Error(`Invalid email address: ${value}`);
}
return value;
}
// Usage
document.querySelector('#signup-form').addEventListener('submit', (e) => {
e.preventDefault();
try {
const email = validateEmail(e.target.email.value);
submitForm({ email });
} catch (err) {
showError(err.message);
}
});
# Python — API request validation
import re
EMAIL_RE = re.compile(r'^[\w.+-]+@[\w-]+\.[a-zA-Z]{2,}$')
def validate_email(value: str) -> str:
value = value.strip()
if not EMAIL_RE.match(value):
raise ValueError(f"Invalid email address: {value!r}")
return value
# Usage in a Flask route
@app.route('/signup', methods=['POST'])
def signup():
try:
email = validate_email(request.json.get('email', ''))
except ValueError as e:
return jsonify({'error': str(e)}), 400
# continue with valid email...
Both use the same underlying pattern. The only meaningful difference: Python’s re.compile() pre-compiles the pattern so repeated calls don’t reparse it on every invocation — worth doing in any hot path.
Keep a Personal Cheat Sheet
The patterns above handle the common cases, but you’ll accumulate your own over time — log formats specific to your stack, identifier schemas from internal systems, date formats your data team insists on using. A short, curated file of tested patterns you can paste from is worth more than memorizing syntax rules. Test them once, annotate them with what they’re for, and reference them rather than rebuilding from scratch each time.
Install Our Extensions
Add IO tools to your favorite browser for instant access and faster searching
恵 Scoreboard Has Arrived!
Scoreboard is a fun way to keep track of your games, all data is stored in your browser. More features are coming soon!
Must-Try Tools
View All New Arrivals
View AllUpdate: Our latest tool was added on Apr 15, 2026
