Regular Expressions 101: A Practical Introduction

Developer Tools - 3 min read - Published 2026-08-27 - Updated 2026-10-19

A regular expression (regex) is a compact pattern for finding text: every date in a log file, every line that starts with ERROR, every word that's repeated twice in a row. Most programming languages, text editors and spreadsheets support them. They look cryptic at first, but a small set of building blocks covers most everyday uses.

Literals and the special characters

Most characters simply match themselves: the pattern cat finds the letters c, a, t in that order, including inside "concatenate". A handful of characters have special meanings: . * + ? ^ $ ( ) [ ] { } | and the backslash. To match one of them literally, put a backslash before it, so \. matches an actual full stop and \$ an actual dollar sign.

The dot matches any single character except a line break, so c.t matches cat, cot and c7t. Forgetting to escape the dot is a classic bug: a pattern meant to match example.com also matches examplexcom.

Character classes

Square brackets match one character from a set: [aeiou] matches any vowel, [0-9] any digit, [A-Za-z] any ASCII letter. A caret at the start negates the set, so [^0-9] matches any character that isn't a digit.

Common sets have shortcuts: \d is a digit, \w is a word character (letters, digits and underscore), and \s is whitespace (space, tab, line break). Their capital versions mean the opposite: \D is a non-digit, \W a non-word character, \S non-whitespace.

Quantifiers: how many times

A quantifier after something says how many times it may repeat. * means zero or more, + means one or more, and ? means zero or one (optional). Braces give exact counts: \d{4} is exactly four digits, \d{2,4} two to four, and \d{2,} two or more.

Worked example: a date in the form 2026-10-19 can be matched with \d{4}-\d{2}-\d{2}. The word colou?r matches both color and colour, because the u is optional.

Anchors: where the match must be

^ matches the start of the text and $ the end, without consuming any characters. ^\d{5}$ matches a string that is exactly five digits and nothing else, while \d{5} on its own would also match five digits inside a longer number. Validation patterns almost always need both anchors.

\b is a word boundary, the point between a word character and a non-word character. \bcat\b matches cat as a whole word but not the cat inside concatenate.

Groups and alternation

Parentheses group parts of a pattern so a quantifier applies to the whole group: (ha)+ matches ha, haha and hahaha. Groups also capture what they matched, so you can extract pieces: in (\d{4})-(\d{2})-(\d{2}), group 1 is the year, group 2 the month and group 3 the day. In a find-and-replace, $1, $2 and $3 (or \1, \2, \3 in some tools) refer to those captures, which lets you reorder a date into 19/10/2026.

The vertical bar means "or": cat|dog matches either word. Combine it with a group to limit its reach: I like (cats|dogs) matches both sentences, whereas I like cats|dogs means "I like cats" or just "dogs".

Greedy vs lazy

Quantifiers are greedy by default: they match as much as possible. Given the text <b>bold</b> and <b>more</b>, the pattern <b>.*</b> matches the whole line from the first <b> to the last </b>, not just the first tag pair. Adding ? after the quantifier makes it lazy, matching as little as possible: <b>.*?</b> matches <b>bold</b> and then, separately, <b>more</b>.

Flags

Flags change how the whole pattern behaves. i makes it case-insensitive, g (global) finds every match instead of only the first, and m (multiline) makes ^ and $ match at the start and end of each line rather than only the whole text. In JavaScript they go after the closing slash, as in /error/gi.

Common mistakes

Over-ambitious validation: perfectly validating email addresses with a regex is practically impossible, and strict patterns reject real addresses. A simple check such as ^[^\s@]+@[^\s@]+\.[^\s@]+$ catches obvious typos; the only real test is sending an email.

Catastrophic backtracking: patterns with nested repetition, such as (a+)+$, can take exponentially long on certain inputs that almost match, freezing a program. Avoid quantifiers inside quantified groups when a simpler pattern will do.

Flavor differences: JavaScript, Python, PCRE (used by PHP) and others share the basics but differ in details such as lookbehind support and named-group syntax. Test patterns in the same flavor your code uses. A regex tester that highlights matches and shows each capture group as you type is the quickest way to get a pattern right.

Related Tools

Related Articles