Regular Expressions Tutorial: A Practical Guide for Developers

May 7, 2026 • 10 min read • Try the free Regex Tester →

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:

PatternMatches
[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
\dAny digit — shorthand for [0-9]
\DAny non-digit
\wWord character: [a-zA-Z0-9_]
\WNon-word character
\sWhitespace: space, tab, newline
\SNon-whitespace

Quantifiers

Quantifiers specify how many times the preceding element can appear:

QuantifierMeaning
*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:

AnchorMatches
^Start of string (or line with multiline flag)
$End of string (or line with multiline flag)
\bWord boundary
\BNon-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:

SyntaxNameExample
(?=...)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

Test your patterns: Use NeatJSON's Regex Tester to build and debug regular expressions with live match highlighting and group capture display.

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.

Related Guides