Skip to content
← All guidesGUIDE · UPDATED 2026-09-24

Regular expressions: a practical guide

The building blocks of regex, working from examples, capture-group replacements, and the backtracking trap that freezes programs.

Regular expressions are a compact language for describing text patterns: “a date”, “a word that starts with a capital letter”, “an email-shaped string”. They are built into every programming language, most editors, spreadsheets, and log tools. They are also easy to get subtly wrong, because a pattern that matches your three test strings can fail on the fourth. This guide covers the building blocks, a way of working that avoids surprises, and the performance trap every developer should know about.

The building blocks

  • Literals match themselves: cat matches “cat”. Characters with special meaning, . * + ? ( ) [ ] { } ^ $ | \, need a backslash to be matched literally, as in \..
  • Character classes match one character from a set: [aeiou], a range such as [0-9], or a negated set such as [^0-9]. Shorthands include \d (digit), \w (letter, digit, or underscore), \s (whitespace), and . (any character except a line break).
  • Quantifiers repeat the previous item: * zero or more, + one or more, ? optional, {3} exactly three, and {2,5} two to five.
  • Anchors match positions, not characters: ^ start, $ end, and \b a word boundary.
  • Groups ( ) treat several items as one unit and capture what they matched. (?: ) groups without capturing, and (?<name> ) captures under a name.
  • Alternation a|b matches either side.

When you meet an unfamiliar pattern, paste it into the regex explainer for a token-by-token description. For example, ^\d{4}-\d{2}-\d{2}$ reads as “start, four digits, hyphen, two digits, hyphen, two digits, end”.

Work from examples, not from memory

The reliable way to write a pattern is to collect real samples first, including ones that must not match, and test against all of them. In the regex tester, paste the samples, write the pattern, and read the list of matches with their exact offsets. Suppose you want capitalised words:

Pattern: \b[A-Z][a-z]+\b   (flag g)
Text:    Ada wrote Toolbox with Grace.
Matches: "Ada" [0–3], "Toolbox" [10–17], "Grace" [23–28]

Then add the awkward cases, such as “McDonald”, “O’Brien”, “Élodie”, and “NASA”, and decide deliberately which should match. A pattern is a specification. Writing down what it should and should not accept is most of the work.

Flags change the meaning

  • g: find every match instead of stopping at the first.
  • i: ignore case.
  • m: ^ and $ match at each line, which is essential for processing lists and logs line by line.
  • s: . also matches line breaks.
  • u: Unicode mode. It treats emoji as one character and enables property escapes such as \p{L} for any letter in any script.

That last point matters for international text: [A-Za-z] does not match “é”, “ß”, or “Ж”. With the u flag, \p{L} does.

Greedy vs. lazy

Quantifiers are greedy by default: they take as much as they can. On <b>one</b> and <b>two</b>, the pattern <b>.*</b> matches the whole string, from the first tag to the last. Adding ? makes the quantifier lazy: <b>.*?</b> matches each tag pair separately. A more precise character class, such as <b>[^<]*</b>, is often clearer and faster still. And for real HTML, use an HTML parser rather than a regular expression.

Search and replace with capture groups

Capture groups turn regular expressions into a powerful editing tool. In the regex replace tester, the pattern (\w+)-(\w+) with the replacement $2, $1 turns “first-last and hello-world” into “last, first and world, hello”. Other useful replacements:

  • (\d{2})/(\d{2})/(\d{4}) → $3-$2-$1 converts DD/MM/YYYY dates to ISO format.
  • ^\s+ with the m flag → nothing removes indentation from every line.
  • $& inserts the whole match, for example to wrap every number in brackets.

Preview the result and check the replacement count before running the same substitution across a codebase or a data export. Note the syntax difference: JavaScript and most editors use $1, while Python and sed use \1.

Catastrophic backtracking

Some patterns take exponential time on certain inputs. The classic example is nested repetition such as (a+)+$. On a string of 30 “a” characters followed by a “b”, the engine tries every way of splitting the a’s between the inner and outer quantifier before giving up. That can freeze a browser tab or take down a server. The attack is called ReDoS, and it has caused real outages at large companies.

The tools on this site refuse constructs that are prone to this, such as nested quantifiers, backreferences, and lookarounds, so the page always stays responsive. In your own code:

  • Avoid nesting quantifiers over overlapping patterns.
  • Prefer specific character classes to .*.
  • Never run user-supplied patterns without a time limit, or use a linear-time engine such as RE2.

Know when not to use a regex

Regular expressions check the shape of text, not its meaning. ^\d{4}-\d{2}-\d{2}$ accepts 2026-02-31. Validate dates with a date library, email addresses by sending a confirmation message, and URLs with a URL parser. For literal find-and-replace, where no pattern is needed, find and replace avoids escaping mistakes entirely.