Regular Expressions Tutorial: A Practical Guide for Developers
Regular expressions (regex) are one of the most powerful tools in a developer's toolkit — and one of the most avoided. They look cryptic at first glance, but once you understand the building blocks, you can write patterns that would otherwise take dozens of lines of string manipulation code.
This tutorial starts from the basics and builds up to real-world patterns used in production code.
Literal Characters and the Dot
The simplest regex is a literal string: /cat/ matches the text "cat" anywhere in the input. Most characters match themselves literally. The exceptions are metacharacters: . ^ $ * + ? { } [ ] \ | ( ) — these have special meaning and must be escaped with \ if you want to match them literally.
The dot . matches any single character except a newline:
/c.t/ → matches "cat", "cut", "c4t", "c t"
Character Classes
Square brackets define a character class — a set of characters to match:
| Pattern | Matches |
|---|---|
| [aeiou] | Any single vowel |
| [a-z] | Any lowercase letter |
| [A-Z0-9] | Any uppercase letter or digit |
| [^abc] | Any character except a, b, or c |
| \d | Any digit — shorthand for [0-9] |
| \D | Any non-digit |
| \w | Word character: [a-zA-Z0-9_] |
| \W | Non-word character |
| \s | Whitespace: space, tab, newline |
| \S | Non-whitespace |
Quantifiers
Quantifiers specify how many times the preceding element can appear:
| Quantifier | Meaning |
|---|---|
| * | Zero or more |
| + | One or more |
| ? | Zero or one (optional) |
| {n} | Exactly n times |
| {n,} | At least n times |
| {n,m} | Between n and m times |
By default, quantifiers are greedy — they match as much as possible. Add ? to make them lazy: *?, +?, ?? match as little as possible.
Input: "<b>bold</b> and <i>italic</i>" Greedy: /<.*>/ → matches the whole string Lazy: /<.*?>/ → matches "<b>" only
Anchors
Anchors don't match characters — they match positions:
| Anchor | Matches |
|---|---|
| ^ | Start of string (or line with multiline flag) |
| $ | End of string (or line with multiline flag) |
| \b | Word boundary |
| \B | Non-word boundary |
/^\d{5}$/ → matches exactly a 5-digit ZIP code (nothing before or after)
Groups and Capturing
Parentheses create capturing groups that let you extract parts of the match:
const match = "2026-05-07".match(/(\d{4})-(\d{2})-(\d{2})/);
// match[1] = "2026", match[2] = "05", match[3] = "07"
Use (?:...) for non-capturing groups (grouping without extracting):
/(?:https?|ftp):\/\// → matches "http://", "https://", or "ftp://"
Alternation
The pipe | acts as OR: /cat|dog/ matches either "cat" or "dog".
Lookaheads and Lookbehinds
These are zero-width assertions that match a position based on what comes before or after, without consuming characters:
| Syntax | Name | Example |
|---|---|---|
| (?=...) | Positive lookahead | /\d+(?= dollars)/ matches "100" in "100 dollars" |
| (?!...) | Negative lookahead | /\d+(?! dollars)/ matches digits NOT followed by " dollars" |
| (?<=...) | Positive lookbehind | /(?<=\$)\d+/ matches "100" in "$100" |
| (?<!...) | Negative lookbehind | /(?<!\$)\d+/ matches digits NOT preceded by $ |
Real-World Regex Patterns
Email Validation (practical, not RFC-complete)
/^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/
URL Validation
/^https?:\/\/([\da-z\.\-]+)\.([a-z\.]{2,6})([\/\w \.\-]*)*\/?$/i
Strong Password (8+ chars, upper, lower, digit, special)
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/
ISO Date (YYYY-MM-DD)
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/
IPv4 Address
/^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/
Regex Flags
g— Global: find all matches, not just the firsti— Case-insensitive:/hello/imatches "Hello", "HELLO"m— Multiline:^and$match line boundaries, not just string boundariess— Dotall:.also matches newline characters
Frequently Asked Questions
What is the difference between .* and .+?
.* matches zero or more characters (can match empty string). .+ matches one or more characters (requires at least one). Use .+ when the content can't be empty.
How do I match a literal dot in regex?
Escape it with a backslash: \. matches a literal period. Unescaped . matches any character.
Why doesn't my regex match across multiple lines?
By default, . doesn't match newlines and ^/$ match only the start/end of the whole string. Use the s flag (dotall) to make . match newlines, and the m flag (multiline) to make ^/$ match line boundaries.