Regular expressions become much easier to write when you build them in small, testable parts. This regex tutorial explains character classes, groups, quantifiers, anchors, lookarounds, capture replacement, and flavor differences, then gives you a repeatable process for testing and maintaining patterns used in validation, search, log parsing, and data cleanup.
Overview
A regular expression, often shortened to regex or regexp, is a pattern that describes text. A program can use that pattern to find a match, extract part of a string, replace text, or check whether an input follows a required format. Regex is useful in many programming languages, but the syntax is not identical everywhere. JavaScript, Python, Java, .NET, PHP, and command-line tools can support different flags, escape rules, and advanced features.
The most reliable way to write a regular expression is to begin with the smallest rule that can work. Suppose you need to find a three-digit code. Start with \d{3}. If the code must appear as a complete value rather than as part of a longer number, add boundaries: ^\d{3}$. If the code must use ASCII digits specifically, consider an explicit class such as [0-9], because shorthand character classes can behave differently across tools and Unicode settings.
Use an online regex tester during development, but test the final expression in the same language and runtime that will execute it. A tester is excellent for exploring matches, captures, and replacements; it is not a substitute for checking the target engine's documentation.
What to track
Character classes and literal text
Literal characters match themselves, while character classes match one character from a defined set:
[aeiou]matches one lowercase vowel.[A-Z]matches one uppercase English letter in engines that interpret the range this way.[^,]matches one character that is not a comma.\dcommonly represents a digit,\wa word character, and\swhitespace, although exact behavior can vary by flavor.
Place a hyphen at the beginning or end of a class when you want a literal hyphen, or escape it if the engine requires that for clarity. Likewise, remember that a dot usually means “any character” rather than a literal period. Match a period with \..
Quantifiers
Quantifiers control how many times an item may occur:
| Pattern | Meaning | Example |
|---|---|---|
* | Zero or more | a* |
+ | One or more | \d+ |
? | Zero or one | colou?r |
{n} | Exactly n | \d{4} |
{n,m} | Between n and m | [A-Z]{2,4} |
Quantifiers are often greedy: they try to consume as much text as possible. Adding ? after a quantifier can make it lazy in many flavors, as in .*?. Do not use a broad expression such as .* by default. A more specific class or a negated class is usually easier to understand and less likely to consume unintended text.
Groups, alternation, and captures
Parentheses group parts of a pattern. For example, (cat|dog) matches either word. The vertical bar means “or,” but its scope is affected by grouping: ^cat|dog$ does not necessarily mean a complete value of either word, while ^(cat|dog)$ does.
Capturing groups store matched text for later use. In a date such as 2026-10-30, (\d{4})-(\d{2})-(\d{2}) captures the year, month, and day. Use a noncapturing group, (?:...), when you need grouping but do not need to extract the result. Named groups, where supported, can make replacement code easier to read than numbered references.
Anchors and boundaries
Anchors describe a position rather than a character. ^ commonly marks the beginning of a string or line, and $ marks the end. The word boundary \b can help find a complete word, so \bcat\b matches “cat” but not “catalog.” Multiline flags can change how beginning and ending anchors behave, so record the flags used with every important pattern.
Lookarounds
Lookarounds check surrounding text without including it in the main match. A positive lookahead such as \d+(?= USD) finds digits followed by “ USD,” while a negative lookahead such as ^(?!admin$).+ rejects the exact value “admin” in engines that support the syntax. Lookbehind works similarly in the other direction, but support varies more widely. When portability matters, a capture group or a second validation step may be clearer.
Cadence and checkpoints
Review a regex whenever the input format, runtime, or business rule changes. For active projects, a monthly or quarterly checkpoint is useful even when the pattern appears stable. The goal is not to rewrite working expressions without reason; it is to confirm that assumptions still match real input.
At each checkpoint, track these variables:
- Accepted examples: Keep representative valid strings, including ordinary and boundary cases.
- Rejected examples: Record malformed, incomplete, ambiguous, or unsafe inputs that must not match.
- Regex flavor: Note the language, runtime, flags, Unicode settings, and any library involved.
- Performance: Test long inputs and repeated structures, not only short examples.
- Replacement behavior: Verify capture numbering or names when using search-and-replace.
A practical test set for an email-like pattern, for example, should include a normal address, an address with a plus tag, a missing domain, leading or trailing spaces, unexpected line breaks, and an unusually long value. Do not claim that a regex proves an address can receive mail; it can only check the syntax your application chooses to accept.
For broader debugging workflows, pair regex testing with a formatter or request inspection tool. The API Debugging Toolkit covers related validation and inspection tasks, while the Developer Tools Toolkit places regex alongside other common utilities.
How to interpret changes
A new match is not automatically an improvement. If a pattern begins matching strings it previously rejected, ask whether those strings are genuinely valid or whether the expression became too broad. If valid inputs stop matching, identify which component changed: a character class, anchor, optional group, flag, or escaping rule.
Compare failures by category rather than editing the pattern randomly. A failure caused by an extra space may call for deliberate whitespace handling. A failure caused by accented characters may indicate that the original ASCII range was too narrow. A failure caused by a newline may involve the dot behavior or a multiline flag. Each fix should have a named test case.
Watch for expressions with nested repetition, such as repeated groups containing .* or another variable-length quantifier. Some backtracking engines can spend excessive time exploring possible paths when a long, carefully constructed input does not match. Prefer bounded quantifiers, explicit character classes, and clear delimiters. If the task involves complex parsing, nested structure, or untrusted large input, a parser or a purpose-built library may be safer than adding more regex syntax.
Keep the expression maintainable by documenting its purpose, flags, expected input, and known limitations. Break complicated patterns into named components in code when the language allows it. For example, define a date part, separator, and time part separately before combining them. A readable multi-line form with comments is often easier to review than a compressed one-line pattern.
When to revisit
Revisit this regex tutorial and your project patterns when you change programming languages, upgrade a runtime, enable Unicode-aware matching, add multiline processing, or move a pattern from a tester into production. Also review patterns when users begin submitting new formats, logs gain new fields, or a cleanup job produces surprising output.
Use this short maintenance routine:
- Write the requirement in plain language before editing the regex.
- Create at least one passing and one failing example for every rule.
- Build the pattern from literals, classes, groups, quantifiers, and anchors in that order.
- Run the examples in an online regex tester, then repeat them in the target language.
- Check flags, escaping, Unicode behavior, replacement references, and long-input performance.
- Save the examples as automated tests and document what the pattern intentionally does not handle.
That final step turns a one-time regex experiment into a dependable programming resource. When the format changes, your test cases will show exactly what changed and whether the pattern still reflects the rule you intended.