Regex for Beginners: 10 Patterns You'll Actually Use Every Day
2026-08-06
Regular expressions have a reputation for being write-only code: a dense string of slashes, brackets, and braces that you copy from Stack Overflow and pray you never have to modify. But the core vocabulary is small — character classes, quantifiers, groups, and anchors — and once those click, the majority of real-world matching tasks boil down to short, readable patterns. This guide skips the exhaustive grammar tour and instead walks through the 10 patterns that come up most often in day-to-day work, explaining every symbol along the way, so you can write and adjust them yourself instead of only pasting them.
What a regular expression actually is
A regular expression (regex) is a tiny language for describing what a string looks like. Given a pattern, a regex engine answers two kinds of questions: does this text conform to the format (validation), and where are the parts that match the format (extraction). Every mainstream language ships with a regex engine, and editors, grep, and log-analysis tools all speak the same dialect.
A minimal example: \d{3}-\d{4} matches strings like 555-0134. Here \d means “any single digit,” {3} means “repeat the previous element three times,” and - is just a literal hyphen. Every regex, no matter how intimidating, is assembled from small parts like these — learn the parts and the whole becomes legible.
Pattern 1: Email address
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
Reading it piece by piece: ^ and $ are anchors meaning “start of string” and “end of string,” so the entire input must match, not just contain a match. [a-zA-Z0-9._%+-] is a character class — any one of the listed characters counts — and the + after it means “one or more times.” The @ is literal. In \., the backslash escapes the dot: an unescaped . means “any character,” so escaping pins it to an actual period. {2,} requires at least two letters for the top-level domain.
A fully RFC 5322-compliant email regex runs over a thousand characters and nobody uses it in practice. The pattern above covers essentially all real addresses you’ll see in a form. If you just want to check whether an address is well-formed without writing any code, an online email validator does the job instantly.
Pattern 2: URL
https?://[\w-]+(\.[\w-]+)+[\w\-.,@?^=%&:/~+#]*
In s?, the question mark makes the preceding character optional, so both http and https match. \w is shorthand for [a-zA-Z0-9_] — letters, digits, and underscore. The group (\.[\w-]+)+ matches dot-separated domain segments like .example.co, and the outer + requires at least one of them. The final character class lists characters commonly legal in paths and query strings, with * allowing zero or more of them.
This pattern is great for pulling URLs out of text. For strict validation, prefer your language’s built-in URL parser and treat the regex as a coarse filter.
Pattern 3: US-style phone number
^\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$
This one reads almost like English once you know the parts: an optional +, an optional country code 1, an optional separator (-, ., or whitespace — note \s matches any whitespace), an optional opening parenthesis, exactly three digits, an optional closing parenthesis, another optional separator, three digits, another separator, and four digits. It accepts 415-555-0134, (415) 555-0134, and +1 415.555.0134 alike.
The deeper lesson: phone formats vary wildly by country, so there is no universal phone regex. For anything international, reach for a dedicated phone-number library instead of a pattern.
Pattern 4: Date in YYYY-MM-DD
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
This pattern showcases alternation inside a group. Parentheses bundle alternatives together and the pipe | means “or.” The month group (0[1-9]|1[0-2]) permits 01–09 and 10–12 while rejecting 00 and 13; the day group similarly allows 01–31.
Keep in mind this only enforces format, not existence — 2026-02-31 still matches even though February never has 31 days. Real date validation needs code. That’s the general rule: regex checks shape, programs check meaning.
Pattern 5: Extracting numbers (decimals and negatives)
-?\d+(\.\d+)?
The leading -? is an optional minus sign. \d+ grabs the integer part, and (\.\d+)? is an optional fractional part: an escaped dot followed by one or more digits, the whole group made optional by the trailing ?. It matches 42, -7, and 3.14.
This is one of the most-used patterns in log parsing and data cleaning. Combined with the global flag (g in JavaScript), it extracts every number from a blob of text in a single pass.
Pattern 6: Trimming whitespace
^\s+|\s+$
\s matches any whitespace character — spaces, tabs, newlines. Used with replacement (replace matches with an empty string), this pattern is a hand-rolled trim(): the two branches on either side of the | eat leading and trailing whitespace in one operation.
To also collapse runs of internal whitespace down to a single space, add a second pass replacing \s{2,} with one space — {2,} means “at least twice.”
Pattern 7: Quoted strings
"([^"\\]|\\.)*"
When parsing CSV or simple config formats, you often need the contents of double-quoted strings. The skeleton is "..."; the interesting part is the middle. [^"\\] is a negated character class matching anything that is neither a quote nor a backslash, while \\. matches escape sequences such as \" or \\. Alternating the two and repeating with * means an escaped quote inside the string won’t end the match early.
Pattern 8: IPv4 address
\b((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b
It looks scary, but the structure is simple. The alternation (25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d) describes one valid 0–255 octet: 250–255, 200–249, 100–199, or anything below 100. Together those four cases cover exactly 0–255, so fakes like 999.1.1.1 are rejected. The first three octets are followed by a dot and repeated with {3}; the last stands alone. \b marks word boundaries so the pattern can’t match a slice out of a longer digit string.
For casual troubleshooting, the looser \b(?:\d{1,3}\.){3}\d{1,3}\b is usually fine — keep the strict version for validation logic.
Pattern 9: Matching non-ASCII text (Unicode ranges)
[\u4e00-\u9fff]+
Character classes aren’t limited to a-z. A class can span any range of code points, and this one — 一 to 龥, the main CJK unified ideograph block — matches runs of Chinese characters. Typical uses are validating that a name field contains Chinese, or extracting the Chinese portions from mixed-language text. The same technique generalizes: [\u0400-\u04ff]+ matches Cyrillic, [\u3040-\u30ff]+ matches Japanese kana. One gotcha worth knowing: punctuation from those scripts lives in different blocks, so a “remove Chinese commas and periods” pattern needs those characters listed separately. In modern JavaScript you can also reach for Unicode property escapes like \p{Script=Han} with the u flag, which reads better and stays correct as Unicode evolves.
Pattern 10: HTML tags
<([a-z][a-z0-9]*)\b[^>]*>(.*?)</\1>
The final pattern introduces two advanced pieces. [^>]* matches the attribute section (anything that isn’t >). (.*?) uses a lazy quantifier so it stops at the nearest closing tag instead of the farthest one. And \1 is a backreference — it requires the text captured by group 1 to appear again, guaranteeing the opening and closing tags have the same name: <div>...</div> matches, <div>...</span> doesn’t.
The standard caveat applies: regex is not a real HTML parser and will break on arbitrarily nested markup. But for grabbing simple, predictable fragments, it’s still the fastest tool in the box.
Greedy vs. lazy: the trap everyone hits once
Quantifiers (*, +, {m,n}) are greedy by default: they consume as much as they can. The classic demonstration is applying <.+> to <b>bold</b> plain <i>italic</i>. You expect it to match <b>; instead it swallows everything from the first < to the very last > on the line, because .+ eats as far as possible before the engine backtracks to find a final >.
Appending ? to a quantifier makes it lazy: match as little as possible. <.+?> yields four short, separate matches — <b>, </b>, <i>, </i>. The rule of thumb: when you’re matching “everything up to some terminator,” reach for a lazy quantifier or, often better, a negated class like [^>]*, which is typically faster and less error-prone.
How to test a regex properly
Shipping an untested regex is the classic beginner mistake. A better workflow:
- Iterate in a visual tester, not in your codebase. Paste real sample data into an online regex tester — it highlights every match live, lists capture groups, and flags invalid syntax, so each tweak gives instant feedback instead of another
console.logcycle. - Build positive and negative test sets. Positives should cover every legitimate variation you expect (emails with
+tags, URLs with ports). Negatives matter just as much: an email missing its@, the date2026-13-45, the IP300.1.1.1— near-misses like these must fail to match. - Watch your flags. The same pattern behaves completely differently with
g(global),i(case-insensitive), orm(multiline, where^and$match per line). Make sure the flags in your tester match the ones in production code. - Beware catastrophic backtracking. Nested quantifiers like
(a+)+can take exponential time on adversarial input and hang a service. If your pattern repeats a repetition, benchmark it against a long non-matching string before deploying.
Nobody memorizes every symbol, and you don’t need to — keep a regex cheat sheet open while you work. Regex is a craft you learn by doing: get comfortable with these ten patterns, and new requirements become small variations on things you already know rather than trips back to a grammar textbook.