Skip to content

Regex Patterns Every Developer Should Know

120 companion flashcards · AI-assisted study content · Open the deck →

This deck is a focused reference for the regex building blocks that come up most often in day-to-day development work. It walks through the essentials, from single-character metacharacters like the dot and digit classes like \d, to shorthands such as \w and \s, and on to the syntax for matching literal characters and escaping special ones. You'll also practice the quantifiers that control how many times a token repeats, the anchors that pin patterns to the start or end of a line, and the lookaheads that let you assert what comes next without consuming it.

The deck is a good fit for developers who want to feel more confident reading and writing regular expressions, whether you are validating input, parsing logs, searching code, or building find-and-replace rules in your editor. If you are brand new to regex, working through these cards in order gives you a solid foundation in the syntax before you tackle more advanced features. If you have used regex casually for years but never quite memorized the difference between greedy and lazy matching, or what \b really does, this set is a great way to firm up the gaps.

Regex is one of those topics where a little daily practice beats long occasional sessions, so try to review a small batch of cards each day rather than cramming. It also helps enormously to keep a regex playground, such as an online tester or your language's built-in evaluator, open while you study, so you can paste in examples and see the behavior change in real time. When a card asks about a tricky symbol like .*? or a lookahead, type out a tiny test pattern right away. Pairing the flashcards with hands-on experimentation will make these patterns stick far longer than reading alone.

Character Matching: Metacharacters, Shorthands, and Classes

A regex engine begins by deciding which characters in the input stream a token can consume. The dot metacharacter . is the most permissive token: by default it matches any single character except the newline character \n. To make it match newlines as well, you enable the dotall mode using the s flag in JavaScript and PCRE, or the re.DOTALL constant in Python. The dot is convenient but dangerous in untrusted input because it can sweep up unwanted characters; always consider a more specific class when the surrounding context is known.

For the most common character categories, regex provides three shorthand classes. \d matches any decimal digit and is equivalent to [0-9] in ASCII mode, but with Unicode awareness it also includes digits from scripts such as Arabic-Indic. \w matches any word character, defined in ASCII as [A-Za-z0-9_] and extended to letters and digits from other scripts in Unicode mode; note that the underscore is always included. \s matches whitespace, including the space, tab \t, newline \n, carriage return \r, form feed \f, and vertical tab \v, with rare forms like the non-breaking space U+00A0 added in Unicode mode. Each of these shorthands has a negation: \D, \W, and \S respectively, which are useful when you need to assert that a position is not of a given type.

When the built-in shorthands are too broad or too narrow, you build your own class with square brackets. A class like [aeiou] matches any one of the listed characters, and you can negate it with a leading caret, so [^aeiou] matches any non-vowel. Ranges use a hyphen between endpoints, as in [a-z] or [0-9A-F] for hexadecimal digits; the hyphen is literal when it appears at the start, end, or right after a backslash inside the class. POSIX-style engines additionally accept named classes enclosed in [:...:], such as [[:alpha:]] or [[:space:]], which are useful when you want locale-aware letter or punctuation matching. To match a literal special character like a dot, parenthesis, or pipe, escape it with a backslash, since most punctuation marks carry metacharacter meaning.

Quantifiers: Greedy, Lazy, Possessive, and Exact

Quantifiers tell the engine how many times to repeat the preceding token. The three fundamental quantifiers are * for zero or more, + for one or more, and ? for zero or one. The difference between * and + matters whenever the pattern might match an empty string: colou?r accepts both color and colour because the ? makes the u optional, while a quantifier like \d+ requires at least one digit and would reject an empty input. When you need a specific count, the brace syntax applies: {3} matches exactly three, {3,} matches three or more, and {3,5} matches between three and five inclusive. + is precisely {1,}; they are interchangeable and you can choose whichever reads better in context.

By default, quantifiers are greedy, meaning they consume as many characters as possible while still leaving room for the remainder of the pattern to succeed. If that remainder fails, the engine backtracks one character at a time until either the overall pattern matches or all possibilities are exhausted. This is essential for correctness, but it has a hidden cost: on adversarial inputs, backtracking can become exponential. To force a quantifier to be lazy, append ? as in .*?, which matches as few characters as possible and expands only as the surrounding pattern demands. The lazy form is the right choice when you want the shortest match, such as extracting a single HTML tag with <.*?>.

Greedy and lazy quantifiers both backtrack; possessive quantifiers do not. A possessive quantifier, written with a trailing + as in .*+, refuses to give characters back once consumed, which makes matching faster and can prevent catastrophic backtracking on patterns like (a+)+$. Atomic groups, written (?>...), achieve the same effect for a whole subexpression: once the group has matched, the engine treats its match as indivisible and never releases characters, even when the surrounding pattern would otherwise force backtracking. Atomic groups are supported in PCRE, Java, and Python 3.11+, and they are an essential tool when you need to combine expressiveness with predictable performance.

Anchors and Boundaries

Anchors do not consume characters; they assert something about the current position in the input. The caret ^ matches the start of the string by default, and with the m flag it also matches the start of each line following a newline. The dollar sign \( matches the end of the string, and with the m flag it also matches just before any newline. Be aware that in JavaScript, even without the m flag, \) will still match before a final newline in some engines, which is a subtle source of off-by-one bugs. For unambiguous absolute positions, PCRE and Python provide \A for the strict start of input and \z for the strict end, with \Z matching end-of-string or just before a final newline.

The word boundary \b is one of the most useful zero-width assertions. It matches the position between a word character (matched by \w) and a non-word character, or at the start or end of the string. The pattern \bcat\b therefore matches the standalone word cat but not the cat inside concatenate. The negation \B matches anywhere that is not a word boundary, which lets you write \Bcat\B to find the substring cat only when it is embedded inside a longer word. Word boundaries are invaluable when searching for keywords in text, because they let you avoid partial matches without listing all possible surrounding characters.

Anchors behave differently inside character classes, and this is a frequent source of confusion. Outside a class, ^ is the start anchor; inside [...], when it is the first character, it negates the class so [^a-z] matches anything that is not a lowercase ASCII letter. Outside a class, \( is the end anchor; inside [\^\)] it is a literal caret or dollar sign. A common idiom ^|$ matches the start or end of a line in multiline mode, but it is sometimes mistakenly written as a character class, which would match a literal caret or dollar sign instead. When in doubt, prefer explicit anchors like \A and \z when you want true start-of-string and end-of-string semantics that no flag can change.

Groups, Captures, and Backreferences

Parentheses do three distinct things in regex, and a good pattern keeps them straight. A capturing group, written (...), records the text it matches so it can be referenced later. Groups are numbered left to right starting at 1, so in (\d{4})-(\d{2}) the first group captures the year and the second captures the month. Captures are powerful: they let you pull structured data out of unstructured strings, and they are the foundation of replacement operations that rearrange text. A non-capturing group, written (?:...), groups tokens for quantifiers or alternation without recording the match, which is the right choice whenever you do not need the captured text. Use non-capturing groups by default to keep numbering predictable and to give the engine slightly less work.

Named capturing groups give meaningful handles to captures. In most flavors the syntax is (?<name>...), with the older Python form (?P<name>...) still supported. You access the captured text by name in code through group("name"), and inside the pattern itself with a backreference like \k<name>. Named groups shine when a pattern has many captures or when the order of groups may change as the pattern evolves. The alternation operator | lets a pattern choose between alternatives at the same position, as in cat|dog. To control precedence or to apply a quantifier to a whole alternative, wrap the alternatives in a group: (?:cat|dog)s? matches cat, cats, dog, or dogs.

A backreference refers to text previously captured by a numbered or named group, and it is one of the most expressive features of regex. The pattern ([abc])\1 matches aa, bb, or cc because \1 requires the next character to be identical to whatever the first group captured. In replacement strings the syntax differs by tool: Perl and JavaScript use $1, while sed and the regex body itself use \1. Backreferences enable patterns that would otherwise require manual parser code, such as \b(\w+)\s+\1\b for finding doubled words or <(\w+)>.*?</\1> for matching balanced HTML open and close tags. Note that backreferences force the engine to be NFA-based, so a regex with them cannot be compiled to a pure DFA.

Lookaround Assertions

Lookarounds are zero-width assertions that test what is ahead or behind the current position without consuming characters. Positive lookahead, written (?=...), asserts that the upcoming text matches the subpattern and then returns the position to where it was, so the surrounding pattern can continue matching. The pattern \d+(?=px) captures the digits in 42px but rejects 42em because the lookahead requires the digits to be followed by px. Negative lookahead, written (?!...), asserts that the upcoming text does NOT match the subpattern, so foo(?!bar) matches foo only when it is not immediately followed by bar. Lookaheads are evaluated at the current position, and the engine does not advance past them, which is why they are perfect for prefix and suffix constraints.

Lookbehinds are the mirror image: they inspect the text before the current position. Positive lookbehind, written (?<=...), asserts that what precedes the position matches the subpattern; negative lookbehind, written (?<!...), asserts the opposite. A practical use is matching a price without including the currency symbol: (?<=\$)\d+(\.\d{2})? matches 19.99 in $19.99 but only when a dollar sign precedes it. Lookbehinds were historically limited to fixed-width subpatterns, which constrained their usefulness, but modern engines have relaxed this. JavaScript supports lookbehinds since ES2018, Python 3.11+ allows variable-width lookbehinds, and PCRE2 supports them fully. When you need a feature that is not yet universal, prefer lookahead, which is widely supported across all flavors.

Lookarounds become particularly powerful when you need to enforce several independent rules in a single pattern. Password validation is the classic example: ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#\(%^&*]{8,}\) uses three positive lookaheads to require at least one lowercase letter, one uppercase letter, and one digit, while the final consuming class with the length quantifier ensures the entire input is at least eight characters and made of allowed symbols. The lookaheads do not consume characters, so each one independently scans the string from the start. This composition is much harder to express with capturing groups or alternation. Lookarounds also make it easy to convert re.match semantics to re.search semantics in Python, because the leading ^ can be replaced with a lookahead that does not consume the start of input.

Flags, Modes, and Engine Features

Flags change the global behavior of a regex. The case-insensitive flag i makes letter matching ignore case, so /hello/i matches Hello, HELLO, and hElLo. In Unicode-aware engines the flag also applies cross-script case folding, so it can match Straße against STRASSE. The multiline flag m changes only the behavior of the anchors ^ and $, making them match at the start and end of each line rather than only at the boundaries of the whole string. It does not change how the dot . behaves, which is the role of the s (dotall) flag. In JavaScript the g flag tells methods like String.prototype.match, matchAll, and replace to iterate every match instead of stopping at the first.

The verbose or extended mode, written x in PCRE and Python, lets you format a pattern with whitespace and # comments for readability. In verbose mode, whitespace inside the pattern is ignored unless it is escaped or appears inside a character class. This is invaluable for complex patterns like the IPv4 octet rule, where breaking the expression across multiple lines with comments can transform an unreadable string into a self-documenting specification. Inline flags let you scope a flag to a subexpression. The empty non-capturing group (?:) serves as a delimiter: (?i:abc) turns on case-insensitivity for abc only, while (?-i) turns it off again. This is useful when a global flag would over-match, for example when matching an identifier case-insensitively but wanting the trailing whitespace to be matched strictly.

Language-specific features fill the gaps that the core regex standard leaves open. In Python, the re module provides re.compile for precompiling patterns used many times, re.search for finding a match anywhere in the string, and re.match for anchoring the pattern at the start of the string (equivalent to prepending \A). The function re.sub performs replacements with backreference support and accepts a callable replacement for dynamic substitutions. JavaScript's String.prototype.replace callback receives (match, ...captures, offset, fullString), which makes it easy to reorder captured groups. PCRE adds a few escapes worth knowing: \K resets the start of the reported match to the current position, useful for replacing only the tail of a match; \R matches any Unicode line break sequence including U+2028 and U+2029; and conditional subpatterns like (?(1)yes|no) branch on whether a captured group matched.

Practical Recipes for Validation and Extraction

Real-world regex patterns solve recurring problems in input validation and text extraction. For an IPv4 address, each octet must lie in the range 0 to 255, which is most cleanly written as the alternation (25[0-5]|2[0-4]\d|[01]?\d\d?), repeated four times with literal dots between them and anchored at both ends. A practical email validator is the deceptively simple ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$, which catches the vast majority of real addresses; the RFC 5321/5322 grammar is far more permissive and cannot be reliably enforced by a single regex, so production systems should always send a confirmation email. A URL pattern typically starts with https?://[^\s/$.?#].[^\s]*, which enforces the scheme and forbids whitespace and common URL delimiters in the host portion.

Date and number validation patterns encode well-known numeric ranges. A date in YYYY-MM-DD form is matched by ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])\(, which enforces a four-digit year, months 01-12, and days 01-31, although it does not check months with fewer than 31 days or leap years. A 24-hour time is ^([01]\d|2[0-3]):[0-5]\d\), with hours constrained to 00-19 or 20-23 and minutes to 00-59. A US zip code with an optional 4-digit extension is ^\d{5}(?:-\d{4})?\(. A positive integer without leading zeros is ^[1-9]\d*\), while a floating-point number with an optional sign and fraction is ^-?\d+(\.\d+)?$; scientific notation can be added with ([eE][+-]?\d+)?.

Several extraction patterns appear in nearly every codebase. A hexadecimal color code is matched by ^#([0-9A-Fa-f]{3}){1,2}$, with the eight-digit form including alpha handled by an additional optional group (?:[0-9a-fA-F]{2})?. A UUID v4 is identified by its 8-4-4-4-12 grouping with a version nibble of 4 and a variant nibble in [89ab]: ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$. To capture the area code from a US phone number like (555) 123-4567, escape the parentheses and write \((\d{3})\)\s*(\d{3})-(\d{4}), producing three captured groups. The body of an HTML <script> tag can be extracted with <script\b[^>]*>([\s\S]*?)</script>, where [\s\S] is a common trick to match any character including newlines without enabling the dotall flag.

Before embedding a regex in production, remember two practical rules. First, never embed raw user input as a regex without escaping metacharacters; use re.escape in Python, Pattern.quote in Java, or the manual replacement str.replace(/[.*+?^\({}()|[\]\\]/g, '\\\)&') in JavaScript. Second, regex is the wrong tool for parsing HTML for security or correctness; even the strip-tags pattern <[^>]+> fails on malformed HTML, comments, and CDATA sections, so prefer a proper sanitizer like DOMPurify for any user-facing context.

Performance and Engine Internals

Modern regex engines fall into two broad families with very different performance characteristics. A DFA, or deterministic finite automaton, runs in linear time, never backtracks, and supports every feature that can be expressed as a finite state machine. The catch is that DFAs cannot support backreferences, lookaround, or capturing groups in their pure form, which is why almost every production regex engine is NFA-based. PCRE, Python's re module, Java's java.util.regex, and JavaScript's engine all use NFA semantics, which means they support the full feature set but may backtrack, and that backtracking can in the worst case be exponential in the input length.

Catastrophic backtracking is the worst-case pathology of an NFA engine. It happens when a pattern has overlapping ways to match the same text, so the engine tries an exponential number of combinations before giving up. The classic example is (a+)+$ applied to a string of many as followed by a single non-a: each outer iteration can split the as into a different number of inner groups, and the engine tries them all. The pattern is technically correct but takes exponential time. The fix is to break the ambiguity: use a possessive quantifier a++ or atomic group (?>a+)+, which prevent the engine from giving back characters once consumed. Whenever you write a pattern with nested quantifiers over the same character class, ask whether the structure is truly unambiguous, and prefer possessive or atomic forms whenever it is.

Several practical habits keep regex fast and predictable. Compile patterns once with re.compile when you reuse them, rather than parsing the pattern string on every match. Avoid backtracking where you can: a negated character class like [^>]+ is much faster than .*? when you know the boundary character. Use lazy quantifiers only when you actually want the shortest match, and use possessive quantifiers or atomic groups whenever the structure allows it. Finally, when a regex becomes hard to read or its performance unpredictable, it is often a sign that a small parser written in your host language would be clearer and faster. Regex excels at small, well-defined matching problems; for anything larger, a real parser is almost always the better tool.

Frequently asked questions

What does the regex metacharacter <code>.</code> match by default?

A single character of any kind except the newline character (\n). To make it also match newlines, use the s (dotall / single-line) flag, e.g. /foo.bar/s in JavaScript or re.DOTALL in Python.

What is a word boundary <code>\b</code>?

A zero-width assertion between a word character (\w) and a non-word character (or the start/end of the string). It does not consume any characters, e.g. \bcat\b matches cat but not concatenate.

What is the difference between greedy and possessive quantifiers?

Greedy quantifiers backtrack to find a match. Possessive quantifiers (written with a trailing +, e.g. .*+ in PCRE/Java) never backtrack, so they can fail faster and prevent catastrophic backtracking. Python 3.11 added possessive quantifiers in the regex module and Python 3.11+ in the re module via inline flags.

What is a simple regex to validate an email address?

A practical approximation is ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$. Note that the official RFC 5321/5322 grammar is far more permissive, and a single regex cannot reliably enforce every rule; always send a confirmation email.

How do you capture the area code from a US phone number like <code>(555) 123-4567</code>?

\((\d{3})\)\s*(\d{3})-(\d{4}). The parentheses must be escaped because ( and ) are metacharacters. The three groups capture area code, prefix, and line number.

What is catastrophic backtracking?

A pathological case where a poorly written pattern causes the regex engine to try an exponential number of combinations before failing. Classic example: (a+)+$ on a string of many as followed by a non-a character. Atomic groups or possessive quantifiers can prevent it.

What does the <code>\K</code> escape do in PCRE / Perl?

It resets the start of the reported match to the current position. Everything matched before \K is consumed but not included in the match. Useful for replacements, e.g. s/foo\Kbar/baz/ replaces only bar in foobar.

How do you match a string that contains a digit somewhere?

.*\d.* or, in a stricter check, \d is enough because any match anywhere in the string counts. To require at least one digit, use ^(?=.*\d).*$ with a lookahead if you plan to add other rules.

How do you match the body of an HTML <code>&lt;script&gt;</code> tag?

A simple pattern is <script\b[^>]*>([\s\S]*?)</script>. The lazy quantifier and the [\s\S] trick (any char including newline) avoid greedy matching of nested or multiple script blocks.

How do you match an empty line?

^\s*$ with the m flag. The ^ and $ delimit an empty line, and \s* allows it to be filled with whitespace (so a line of only spaces also matches).

Drill this topic

120 flashcards on Regex Patterns Every Developer Should Know — free, no signup needed to start.

Study Regex Patterns Every Developer Should Know flashcards

LearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.