Regular Expressions 101: A Practical Introduction

Developer Tools - 7 min read - Published 2026-08-27

Regular expressions have a reputation for being unreadable, and a lot of that reputation is earned - but the core building blocks are a small, learnable set, and most real-world regex use only combines a handful of them.

The Core Building Blocks

A literal character matches itself. A dot (.) matches any single character except a newline. Square brackets [abc] match any one character inside them, and [^abc] matches any character NOT inside them. A backslash-d (\d) matches any digit, \w matches any word character (letters, digits, underscore), and \s matches any whitespace.

Quantifiers control repetition: * means "zero or more," + means "one or more," ? means "zero or one," and {n,m} means "between n and m times." Anchors pin a match to a position: ^ means the start of the string (or line), and $ means the end.

Greedy vs. Lazy Matching

By default, quantifiers are greedy - they match as much as possible before backtracking if needed. This causes a very common bug: matching <.*> against "<b>bold</b>" greedily grabs the entire string from the first < to the very last >, not just <b>, because .* tries to consume as much as it can first. Adding a ? after the quantifier (making it .*?) makes it lazy instead - matching as little as possible - which usually gives you the result people actually expect in cases like this.

Capture Groups

Parentheses create a capture group, which does two things: it groups a sub-pattern so quantifiers can apply to the whole group at once, and it extracts that matched portion separately from the full match. Named capture groups, written as (?<name>...), let you refer to a specific captured value by name instead of by position - genuinely useful once a pattern has more than two or three groups and counting positions gets error-prone.

The Most Common Mistake

The single most common regex mistake is forgetting to escape special characters that need to be matched literally. A period (.) inside a pattern matches any character, not a literal dot - so matching a real period requires \. instead. This bites people constantly when validating things like file extensions or IP addresses, where an unescaped dot will silently accept input it shouldn't.

A Practical Approach

Build patterns incrementally against real test cases rather than trying to write the whole thing at once from memory - test against strings that should match, and just as importantly, strings that shouldn't. A regex that's too permissive is a much more common source of bugs than one that's too strict, precisely because it fails silently instead of throwing an obvious error.

Related Tools

Related Articles