Regular Expressions (Regex), Explained

By Udit Agarwal · Updated October 17, 2026

A regular expression, or regex, is a pattern that describes a set of strings, used to search, validate, extract and replace text. \d{3}-\d{4} finds a number like 555-0123, and ^\d+$ checks that a whole string is digits. Most languages and editors share the core syntax, though flavors differ in the details; the examples here use JavaScript's, which browsers and the ArrayKit Regex Tester run.

Literals, character classes and anchors

Most characters match themselves: cat finds those three letters anywhere, including inside concatenate. A handful have special meanings and need a backslash to be matched literally: . ^ $ * + ? ( ) [ ] { } | \. So \. matches a period and \? a question mark.

A character class in square brackets matches one character from a set. [aeiou] matches one vowel, [a-z] one lowercase letter, and [^0-9] any character that is not a digit. Shorthand classes cover the common sets: \d is a digit, \w a word character (A–Z, a–z, 0–9 and underscore), \s whitespace, and the uppercase versions \D, \W and \S are their opposites. A dot matches any character except a line break.

One JavaScript surprise: \w only knows ASCII, even with the u flag. Run against Ünïcödé it finds just n, c and d. For letters in any language, use \p{L} with the u flag.

Anchors match positions, not characters. ^ is the start of the input and $ the end, while \b is a word boundary, so \bcat\b matches cat in "the cat sat" but not in "concatenate". Anchors are what turn a search into a validation: \d+ finds 123 inside abc123, while ^\d+$ rejects it.

Quantifiers: greedy and lazy

A quantifier says how many times the item before it may repeat:

  • * zero or more, + one or more, ? zero or one, so colou?r matches both "color" and "colour".
  • {3} exactly three, {2,} two or more, {2,5} between two and five.

Quantifiers are greedy by default: they take as much as they can, then give characters back only if the rest of the pattern fails. On <b>bold</b> text, the pattern <.+> matches all of <b>bold</b>, because .+ runs to the last >. Adding ? makes a quantifier lazy, taking as little as possible, so <.+?> matches <b> and </b> separately. A negated class is often better still: <[^>]+> cannot run past a >.

Groups, alternation and backreferences

Parentheses group part of a pattern, as in (ha)+, and capture what that part matched. Run against Released 2026-10-17., the pattern (\d{4})-(\d{2})-(\d{2}) captures 2026, 10 and 17. Named groups label the parts: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}). When you need grouping without capturing, write (?:…).

Captures are what make replacements useful. In JavaScript, '2026-10-17'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$3/$2/$1') returns 17/10/2026.

The pipe means "or", and it has the lowest precedence of anything in a pattern. ^cat|dog$ means "starts with cat, or ends with dog", so it matches catdog. To accept exactly one of the words, group them: ^(?:cat|dog)$.

A backreference matches the same text a group captured earlier, using \1 or \k<name>. With the g and i flags, \b(\w+)\s+\1\b finds doubled words, flagging is is, test test and The the in "This is is a test test. The the end."

Lookahead and lookbehind

Lookarounds check what comes before or after a position without including it in the match: (?=…) means "followed by", (?!…) "not followed by", (?<=…) "preceded by" and (?<!…) "not preceded by".

  • \d+(?= dollars) finds 100 in "100 dollars and 200 euros".
  • (?<=\$)\d+(?:\.\d{2})? finds 42.50 and 3 in "Total: $42.50, tax $3, 7 items".
  • ^(?=.*\berror\b)(?=.*\bdisk\b) matches lines containing both words, in either order.

Stacked lookaheads are how sites enforce rules such as "at least one digit", although current NIST guidance tells systems not to impose them (see what makes a password strong). Watch how lookarounds interact with backtracking: \d+(?! dollars) still matches 10 in "100 dollars", because 10 is followed by "0 dollars". Adding boundaries, as in \b\d+\b(?! dollars), fixes it.

Flags in JavaScript

Flags change how the whole pattern behaves:

Flag Name Effect
g global Find every match, not just the first
i ignore case hello also matches HELLO
m multiline ^ and $ also match at line breaks
s dotAll . also matches line breaks
u unicode Works on full code points and enables \p{…}
y sticky Matches only at the exact position in lastIndex

In three lines reading "12", "ab" and "34", ^\d+$ finds nothing until m is on, then finds 12 and 34. Without u, ^.$ does not match the single emoji 😀, because JavaScript stores it as two UTF-16 units, and \p{L} silently matches the literal text p{L}. Recent engines add d (match positions for groups) and v, a stricter successor to u.

One trap in code: a regex with g remembers where its last match ended, so repeated .test() calls on the same object can alternate between true and false on identical input.

Regex cheat sheet

Pattern Matches
. Any character except a line break
\d \w \s Digit, word character, whitespace (\D \W \S negate them)
[abc] [^abc] [a-z] One of, none of, a range
^ $ Start and end of input, or of a line with m
\b \B Word boundary, not a word boundary
* + ? Zero or more, one or more, optional
{n} {n,} {n,m} Exactly n, at least n, n to m
*? +? ?? Lazy versions
(…) (?:…) (?<name>…) Capture, group only, named capture
\1 \k<name> Backreference
a|b a or b
(?=…) (?!…) Followed by, not followed by
(?<=…) (?<!…) Preceded by, not preceded by
\. \\ A literal dot, a literal backslash

Common patterns, with honest limits

Task Pattern Limit
Rough email check ^[^\s@]+@[^\s@]+\.[^\s@]+$ Accepts ada@example..com and rejects valid quoted addresses
Date in YYYY-MM-DD form ^\d{4}-\d{2}-\d{2}$ Accepts 2026-13-45
US ZIP code ^\d{5}(?:-\d{4})?$ Checks the format, not that the code exists
Hex color ^#(?:[0-9a-fA-F]{3}){1,2}$ Rejects 8-digit colors with alpha, like #1e90ff80
Trailing spaces [ \t]+$ with g and m Ignores other Unicode spaces
Doubled words \b(\w+)\s+\1\b with g and i ASCII words only

A regex can only check the shape of a value. Even the stricter date pattern ^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$ accepts 2026-02-31, and no pattern can tell whether an email address actually exists.

The Regex Tester's opening example is a simple email finder, \b[\w.]+@[\w.]+\.\w+\b. It shortens user+tag@example.com to tag@example.com and misses ada@my-site.com entirely, because neither + nor - is in [\w.]. For structured text such as URLs, a parser beats a pattern: the URL Parser splits an address properly, and URL encoding, explained shows why the same address can be written several ways.

Catastrophic backtracking

When part of a match fails, the engine backs up and tries other ways of dividing the text among the quantifiers. Usually that is cheap, but when quantifiers are nested over the same characters, the number of ways grows exponentially and a failing match tries every one.

^(a+)+$ is the textbook case. Against a run of as ending in !, each extra a roughly doubles the work: in Node.js on a laptop, 24 of them took about 0.1 seconds, 28 about 3 seconds and 30 about 8 seconds. Real patterns hide the same flaw. ^(\w+\s?)*$, meant as "words separated by single spaces", took about 45 seconds to reject a 50-character line of ten words ending in !. Rewritten as ^(?:\w+\s)*\w*$, which accepts exactly the same strings, it rejected a 10,000-word line in under a millisecond.

When attackers trigger it on purpose it is called ReDoS, regular expression denial of service, and it has caused real outages. In July 2019, a single regular expression in a firewall rule backtracked so heavily that it exhausted the CPUs serving Cloudflare's traffic and took its network down for 27 minutes. To stay safe:

  • Never nest quantifiers that can match the same text, as in (a+)+, (\w+\s?)* or (.*)*.
  • Prefer specific classes such as [^"]* to .*, and make alternatives mutually exclusive.
  • Limit input length, and never run user-supplied patterns on a server without a time limit.

Test patterns with the ArrayKit Regex Tester

The Regex Tester runs your pattern with JavaScript's built-in RegExp engine, so it follows the same rules as browsers and Node.js. Type the pattern without the surrounding slashes, paste a test string, and switch the g, i, m, s and u flags on or off. It highlights every match in the text, counts them, and lists the first 50 with each match's start position, its text, its numbered groups (an unmatched group shows as an empty string) and its named groups. An invalid pattern shows the engine's own error message.

The tester always lists every match, even with g switched off, and it stops collecting after about 5,000 matches. It has no replace mode and no toggle for y, d or v. It re-runs the pattern as you type, inside the page, so a catastrophic pattern can freeze the tab; try risky patterns on short text first. Patterns and text stay in your browser and are not uploaded.

Shell-style wildcards are a different syntax: Glob to Regex converts a pattern like src/**/*.ts into a JavaScript regex and tests it against sample paths.

Frequently asked questions

What is a regular expression? A regular expression is a pattern that describes a set of strings. Programs use it to find, validate, extract or replace text, for example finding every date in a log or checking that a field contains only digits.

What is the difference between greedy and lazy matching? A greedy quantifier matches as much text as it can and gives some back only if needed, while a lazy one, written with an extra question mark such as +?, matches as little as possible.

What does the g flag do? It makes a regex find every match instead of stopping at the first. In JavaScript it also makes the regex remember where the last match ended, which affects repeated calls to test() and exec().

Can a regex validate an email address? Only roughly. The address format allows far more variety than a practical pattern accepts, and a well-formed address may not exist. Use a simple pattern to catch typos and confirm the address by sending an email.

Which regex flavor does the ArrayKit Regex Tester use? JavaScript's. It runs the browser's own RegExp engine, so a pattern behaves there as it will in your JavaScript code, with the g, i, m, s and u flags available as toggles.

What is catastrophic backtracking? It is what happens when a pattern can match the same text in exponentially many ways, often because of nested quantifiers. A failing match then tries every combination and can take seconds, minutes or longer on a short string. Rewriting the pattern so each character can be matched only one way fixes it.

Keep going

Probador de regex

More guides

  • HEX, RGB and HSL Color Codes, Explained — How HEX, RGB and HSL color codes work, how to convert between them step by step, what alpha and 8-digit hex mean, and which WCAG contrast ratios text needs.
  • Markdown Syntax, Explained — A Markdown cheat sheet and guide: headings, lists, links, images, code, tables and task lists, plus the line-break and escaping rules that trip people up.
  • Text Diff, Explained — How a text diff works: line, word and character diffs, the longest common subsequence, reading added and removed lines, and the whitespace traps to avoid.
  • Base64 Encoding, Explained — Base64 explained: how it turns binary into text, the alphabet and padding, the URL-safe variant, size overhead, use cases, and why it isn't encryption.
  • Linux File Permissions and chmod, Explained — What chmod 755, 644 and 777 mean: owner, group and others, read, write and execute, octal and symbolic modes, umask, setuid, setgid and the sticky bit.
  • DMS and Decimal Degrees, Explained — How to convert DMS to decimal degrees and back, how DD, DMS and DDM differ, how many decimal places you need, and which formats Google Maps and GPS accept.

All Code Formatters tools · All guides