FormatFlow

No tool matches “”

Try a format (“webp”, “mp3”) or a verb (“merge”, “compress”).

Loading index

↵to open

how-to · 6 min read

Regular expressions: a practical starting guide

A regular expression is a pattern that describes a set of strings. It can check whether text matches, find every occurrence, capture parts of a match, or find and replace with references to those captures. Most programming languages and many command-line tools implement the same core syntax with small differences.

The difficulty is not writing a pattern once; it is understanding why a pattern matches more or less than intended. This guide covers the building blocks, the matching model that explains surprising results, and the safety limits worth knowing before you run a pattern on untrusted input.

By FormatFlow EditorialUpdated October 20, 2026More Developer Tools →

A regular expression is a description, not a search string

A literal search finds the exact characters you typed. A regular expression interprets some characters as instructions, so the pattern abc matches the characters abc, while a.c matches any three-character string that starts with a and ends with c. That interpretation is what makes regex powerful and what makes an unescaped dot a common bug.

Regular expressions are usually applied to a line or a whole string depending on flags, and that context changes the result. The same pattern might match one substring on a single line and something entirely different once newlines are included. Always confirm whether the engine treats the input as one string or as separate lines before debugging the pattern itself.

The building blocks worth learning first

Character classes match one character from a set: [abc] matches a, b or c; [a-z] matches any lowercase ASCII letter; d is a digit; w is a word character; s is whitespace. Negated classes flip the meaning, so [^0-9] matches anything that is not a digit. Anchors do not consume characters: ^ matches the start of the string or line and $ matches the end.

Quantifiers say how many times the previous element repeats: * means zero or more, + means one or more, ? means zero or one, and {2,5} means between two and five. By default they are greedy and take as much as possible, then give characters back only if the rest of the pattern needs them; adding ? makes them lazy and takes as little as possible. Groups in parentheses capture what matched, and a pipe creates alternatives such as cat|dog.

  • d+ matches one or more digits; ^d{5}$ matches exactly five digits on its own.
  • word matches the whole word, not the word inside another word.
  • ([A-Za-z]+)s+([A-Za-z]+) captures two words separated by whitespace.
  • a.*?b matches from the first a to the nearest b, while a.*b reaches the last b.
  • Flags change behaviour: i ignores case, g finds all matches, m treats ^ and $ as line anchors, s lets dot match newlines, u enables full Unicode semantics.

How matching actually proceeds

Engines try the pattern at successive positions in the input (unless it is anchored), and at each position they explore alternatives. When a greedy quantifier has consumed characters and the rest of the pattern fails, the engine backtracks and tries a shorter repetition. That backtracking is why a pattern can match correctly and still be slow: the number of paths it explores can grow exponentially with the input.

Global matching is a loop around that process. A pattern with the g flag returns successive matches, and zero-length matches advance the position specially to avoid an infinite loop. Capture groups are numbered by the order their opening parenthesis appears, and named groups make complex patterns far easier to read and maintain.

A debugging workflow that works

Start from the smallest input that should match and the nearest input that should not. Test both, then add complexity one element at a time instead of writing the full pattern and staring at the result. When a match is wrong, ask three questions: where did the match start, where did it end, and which alternative or repetition consumed the characters in between.

Escape characters that the engine treats specially. This is the most common source of confusion when testing URLs, file paths, email addresses and code snippets. Remember that JSON and programming-language string literals also process backslashes, so a pattern written for a regex engine may need the backslash doubled in source code. Use a tester that shows both the pattern you typed and the pattern the engine received.

Safety: catastrophic backtracking and untrusted input

Some patterns are correct but catastrophically slow on particular inputs. Nested quantifiers such as (a+)+$ are the classic example: for a long run of characters followed by one that cannot match the end anchor, the engine tries an exponential number of ways to divide the run. The practical defences are to simplify nested repetition, anchor patterns where possible, prefer explicit character classes, and enforce a timeout or input-length limit when matching untrusted text.

Never use a regular expression as the only defence for security-sensitive parsing. Email addresses, HTML, URLs and programming languages all exceed what a compact pattern can validate correctly. Use a regex for a quick sanity check, then parse with a dedicated library or validate structurally. And when a pattern runs on a server, cap the input size and the number of matches so a single request cannot consume the process.

Frequently asked questions

Why does my regex match only the first occurrence?
Most string methods return the first match only. Add the g flag when using an engine that supports it, or use a global find-all function such as matchAll. The tester shows all matches by default, so if it finds several and your code finds one, the flag is usually the difference.
What is the difference between greedy and lazy quantifiers?
Greedy quantifiers consume as much as possible and then backtrack; lazy quantifiers consume as little as possible and expand only when needed. Adding a question mark after the quantifier makes it lazy: a.*b reaches the last b on the line, while a.*?b stops at the first b.
Why does my pattern work in one language and fail in another?
Regular expression dialects differ in supported features and defaults. Lookbehind, named capture syntax, Unicode property escapes and the meaning of w vary by engine. Also check how the pattern is passed: JSON or string escaping can change the backslashes before the engine ever sees them.
How do I match across multiple lines?
By default dot does not match a newline. Enable the s flag to make dot match newlines, and use the m flag if you want ^ and $ to match at each line boundary rather than only at the start and end of the whole string. Both flags are available in JavaScript and many other engines.
Is a regex enough to validate an email address or a URL?
No. A regex can catch obvious mistakes, but complete validation requires parsing with a library that implements the actual specification. Use a permissive pattern to reject clearly invalid input and let the real parser decide whether the value is usable.

Tools for this job

All Developer Tools