Home · Blog · Regex basics

Regular expressions: the 20% you'll use 80% of the time

10 Oct 2026 · Text · 6 min read

Regular expressions have the worst reputation in programming, and it's mostly undeserved. Yes, a full regex engine is a small language of its own, and yes, some patterns look like a cat walked across the keyboard. But you don't need the whole language. A small handful of ideas — literals, character classes, quantifiers, anchors, groups — handles the overwhelming majority of real text jobs: finding dates in a log, validating a form field, renaming a thousand files, pulling email addresses out of a spreadsheet column.

This article is that handful. No automata theory, no exhaustive reference. Just the patterns you'll actually reach for, with examples you can test as you read. If a pattern below looks useful, try it in your editor's find box — nearly every code editor, from VS Code to Sublime to vim, speaks the same basic dialect.

Start with the literal: text matches itself

The first thing to know is also the most reassuring: ordinary characters in a regex match themselves. The pattern cat matches the three letters "cat" wherever they appear — inside "cat", inside "concatenate", inside "certificate". There's no magic until you ask for it.

Two characters are magic from the start. The dot . matches any single character except a newline, so c.t matches "cat", "cut", "c3t" and "c t". And the backslash \ removes the magic: \. matches a literal full stop, which is what you want when you're hunting for the end of a sentence rather than any character at all. A dozen or so characters are special in regex — . ^ $ * + ? { } [ ] \ | ( ) — and the rule is simple: if you mean the character itself, put a backslash in front of it.

Character classes: matching a set of characters

Square brackets define a set: [aeiou] matches any single vowel. Ranges use a hyphen: [a-z] matches any lowercase letter, [0-9] any digit, [A-Za-z] any letter at all. Put a caret first and the set inverts: [^0-9] matches any single character that is not a digit.

Because these sets come up constantly, regex gives you shorthands:

ShorthandMeansOpposite
\dAny digit (0–9)\D — anything but a digit
\wWord character: letters, digits, underscore\W — anything else
\sWhitespace: space, tab, newline\S — anything but whitespace

So \d\d\d matches any three digits in a row — a pattern you'll see again in the greatest-hits table below. Note that \w includes the underscore and digits, which surprises people matching "words" in prose; [A-Za-z] is the stricter choice when you mean letters only.

Quantifiers: saying how many

Real text is repetitive, and quantifiers are how you say "more of that":

  • * — zero or more. ab*c matches "ac", "abc", "abbbbc".
  • + — one or more. \d+ matches "7", "42", "1000000" — any whole number.
  • ? — zero or one (optional). https? matches "http" and "https".
  • {n} — exactly n. \d{4} matches a four-digit year.
  • {n,m} — between n and m. \w{3,12} matches a username of 3 to 12 word characters.

The troublemaker is *, because "zero or more" includes zero: the pattern a* matches the empty string at every position, which is why beginners get confusing match counts. When you mean "at least one", reach for + instead — \d+ for numbers, \s+ for gaps between words, .+ for "the rest of the line". As a rule of thumb, + is the quantifier you'll use ten times more often than *.

Anchors: saying where in the line

So far every pattern could match anywhere. Anchors pin it down: ^ means "start of the line" and $ means "end of the line". The pattern ^Chapter matches "Chapter" only at a line's start; \d+$ matches digits only at a line's end. Combine them — ^\d+$ — and you've described "a line containing nothing but digits", which is the standard trick for validating that a whole input is a number and not just contains one.

The third anchor, \b, is a word boundary: the invisible fence between a word character and a non-word character. \bcat\b matches the word "cat" but not the "cat" inside "concatenate" — the single most useful pattern in this article if you ever search prose.

Groups and alternation: this or that

Parentheses do two jobs. First, they group, so a quantifier applies to more than one character: (ab)+ matches "ab", "abab", "ababab". Second, with a pipe inside, they offer alternatives: (cat|dog|bird) matches any of the three words. You'll use this constantly for small variations — colou?r handles "color" and "colour" with the optional-u trick, and (Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day matches every day of the week.

Groups also capture: most tools remember what each group matched, so you can refer back to it. In a find-and-replace, (\w+)@(\w+) replaced with $2@$1 would flip "user@host" into "host@user". You won't need this daily, but when you do, nothing else substitutes.

The greatest hits: patterns worth memorizing

With the five ideas above you can read — and adapt — the patterns that turn up everywhere. Here are the ones worth committing to memory:

JobPatternNotes
Four-digit year\b\d{4}\bThe boundaries stop "12345" matching
Date (YYYY-MM-DD)\d{4}-\d{2}-\d{2}Matches format, doesn't validate the date
Hex colour#[0-9a-fA-F]{6}\bAdd ([0-9a-fA-F]{3}){1,2} for 3-digit too
Simple email-ish[\w.+-]+@[\w-]+\.[\w.]+Catches typos, not a real validator (see below)
URL-ishhttps?://\S+Good enough for finding links in text
Trim whitespace^\s+|\s+$Replace with nothing; alternation at work
Duplicate words\b(\w+)\s+\1\bFinds "the the" — a proofreader's friend

Greedy versus lazy: the one surprise

Quantifiers are greedy by default: they grab as much as they can. On the text <b>hi</b> <i>yo</i>, the pattern <.*> matches the entire string from the first < to the last > — one big match, not two tags. Add a question mark — <.*?> — and the quantifier turns lazy, matching as little as possible: now you get <b>, then </b>, and so on. Whenever a pattern matches "too much", laziness is the first thing to try.

Where regex stops being the answer

Honesty requires the caveats. First, regex can't count nesting — it cannot reliably match balanced parentheses or parse HTML, because those need memory of depth. The famous advice stands: to parse HTML, use an HTML parser, not a pattern. Second, some patterns are explosively slow: (a+)+$ tested against twenty a's followed by a "b" can take effectively forever, a trap called catastrophic backtracking. If a pattern hangs, simplify the nesting. Third, "validating" an email address with regex is a rite of passage that ends in tears — the real specification is hundreds of pages long, and every strict pattern rejects some valid address. For forms, check for an @ and a dot, then send a confirmation email; that's the only validation that ever worked.

But within its lane — finding, extracting and tidying text — nothing beats it. Learn the five ideas above and you'll recognize 80% of the patterns you'll ever meet, and write most of the ones you'll ever need.

Count the words