Regex patterns every developer should know
Most regular expressions people actually need in day-to-day work come from a small, reusable set. Memorising the syntax matters less than knowing which pattern to reach for.
Whitespace and trimming
`^\s+|\s+$` matches leading and trailing whitespace, which is what `String.prototype.trim()` does internally. `\s+` collapses runs of spaces, tabs and newlines into one match, useful when normalising pasted text before storage.
Emails: good enough, not perfect
A fully RFC 5322-compliant email regex is enormous and still wrong at the edges. For form validation, a pragmatic pattern works better paired with a confirmation email:
const emailLike = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;This rejects obviously malformed input without pretending to be a full validator. Never use regex alone to decide whether an email address can actually receive mail.
URLs and paths
`^https?:\/\/[\w.-]+(?:\/[\w./?%&=-]*)?$` catches the common case of an http(s) URL with an optional path and query string. For anything more precise, parse with the URL API instead — a URL parser tool exists exactly because regex is the wrong tool for full RFC 3986 compliance.
Capturing groups vs non-capturing groups
Use `(?:...)` when you need to group alternatives but do not need the matched text back — it is faster and keeps your capture group indices predictable when the pattern grows.
const version = /^v(?:ersion)?[\s-]?(\d+)\.(\d+)\.(\d+)$/;Here only the three numeric groups are captured; the optional "ersion" text is grouped but ignored.
Lookaheads for validation without consuming
Password strength rules are the classic use case:
- `(?=.*[a-z])` — at least one lowercase letter
- `(?=.*[A-Z])` — at least one uppercase letter
- `(?=.*\d)` — at least one digit
- `(?=.*[^\w\s])` — at least one symbol
Chain lookaheads at the start of the pattern, then follow with the length requirement: `^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$`.
Greedy vs lazy quantifiers
`.*` is greedy and will match as much as possible before backtracking. `.*?` is lazy and matches as little as possible. Extracting content between HTML-like tags is the classic trap:
"<b>one</b><b>two</b>".match(/<b>(.*)<\/b>/)[1]; // "one</b><b>two"
"<b>one</b><b>two</b>".match(/<b>(.*?)<\/b>/)[1]; // "one"Escaping special characters
If you are building a pattern from user input, escape regex metacharacters first, or the input can change what the pattern matches entirely:
function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}Catastrophic backtracking
Nested quantifiers like `(a+)+` can take exponential time on pathological input — a classic ReDoS. Avoid nesting repetition, prefer atomic patterns, and test suspicious patterns against a long string of near-matches before shipping them anywhere user input reaches the pattern.
Test before you commit
Write the pattern, then run it against a batch of both matching and non-matching examples in a regex tester with live highlighting. Seeing which group matched what removes almost all of the guesswork, especially with lookaheads and nested groups.