Everything the adaptive question bank can teach and test in Regex, from foundations through advanced practice. Work through it in order, or start practicing and let the questions find your level.
Thinking of it as a set rather than a search string is what makes the rest predictable: every construct either widens that set or narrows it. Asking which strings a pattern admits catches more mistakes than tracing an example through it.
A span rather than the whole input is why a validator built on a bare pattern accepts anything containing a valid value. Anchoring both ends, or using a whole-string API, is the difference between finding and validating.
The default is the generous one, which is convenient for searching and wrong for checking. Most patterns that accept obviously bad input are missing nothing more than their two anchors.
Reporting the FIRST start that works, rather than the best one, explains results that look suboptimal — the engine never compares candidates. Every entry about alternation ordering follows from this one.
Asking the cheaper question when you only need a yes answer saves the engine recording every group. Reaching for the extracting call and ignoring its result is a common and invisible waste.
Succeeding everywhere is technically correct and almost never what was meant, so a pattern built entirely from optional parts validates nothing. Requiring at least one of something is the usual repair.
The set is small enough to memorize, which is worth doing once: everything outside it needs no escaping, and escaping it anyway is legal in some flavors and an error in others.
Being exact by default is the safe choice, and the flag is a broader change than it looks — under Unicode it performs full folding, which brings in pairs nobody expects. That is its own entry later.
Treating a user's string as a pattern hands them control of the match, and in a backtracking engine of the CPU as well. Escaping is the fix, and it is a different fix from bounding the input.
B. Literal characters and escaping
Stripping the special meaning is the whole mechanism, and it works in both directions: the same character also GIVES meaning to an ordinary letter, which is why the escape list has to be learned as a set.
The forward slash is on the list only inside a literal, which is why a pattern copied out of one works unchanged in a constructor and a pattern copied in does not. Flavor differences start here.
Harmless in legacy mode and a syntax error under the newer flags is the kind of difference that turns a working pattern into a crash on the day someone adds a flag. Escape only what needs it.
These name characters that are otherwise hard to type, and the last one is the trap: the null escape has a different meaning once a digit follows it, which is the octal ambiguity entry.
The braced form is the only one that reaches beyond the basic plane, and it is legal only under the newer flags — without them the braces read as a repetition count, which fails silently rather than loudly.
The string literal consumes one level of escaping before the pattern ever sees it, so every backslash has to be written twice. Raw-string syntax in other languages exists precisely to remove this step.
Far fewer characters are special here, and position often substitutes for escaping — a hyphen first or last is literal. Adding a backslash anyway is tolerated by some engines and rejected outright by others.
A search term is data and a pattern is code, and nothing in the type system marks the difference. Using the built-in escaper rather than a hand-rolled one matters because the metacharacter set differs by flavor.
C. Character classes
Exactly one is the part to hold onto, because the brackets look like a group and behave like a single character. Repeating the set is what a quantifier after it does, and that is a separate decision.
This is the commonest first misreading, and it is silent: the pattern still matches plenty of things, just not the word you meant. A sequence needs no brackets at all.
Ranges run over codepoints rather than over alphabets, which is why a range crossing case picks up the punctuation that sits between them. Writing the two halves separately costs nothing and says what you meant.
Negation flips which characters qualify and does not make the construct zero-width, so it still needs one character to be there. That is why it can never succeed at the very end of the subject.
Accepting a line terminator where the wildcard would not is an asymmetry that catches people building line-oriented patterns. If a newline must be excluded, it has to appear inside the negated set.
Three positions make it literal, which is why so many patterns containing one are correct by accident. Escaping it is the option that survives someone later reordering the set.
Only the leading position negates, so the character is ordinary anywhere else in the set. That makes an accidental negation unlikely and an accidental literal rather common.
Unioning shorthands with literals is what makes a class the compact way to say a messy requirement. The negated shorthands can be mixed in too, which is how the double-negation trap gets built.
Combining a shorthand with its own complement inside one negated set asks for a character that is neither, which nothing satisfies. The pattern compiles and simply never matches, which is the worst kind of failure.
One comparison against a set is cheaper than several branches the engine must try and unwind. For single characters the class is both faster and clearer, and the difference shows up under repetition.
D. Shorthand class escapes and the wildcard
Excluding line terminators by default is what makes the wildcard line-oriented, and it is the single most surprising default for anyone treating it as any character. The flag changes it globally rather than locally.
The same two characters mean an ASCII range in one language and every Unicode digit in another, so a validator ported between them silently widens. Spelling the range out removes the ambiguity entirely.
The underscore is in the set, which quietly makes an identifier pattern accept more than a word pattern should. It also propagates: the boundary assertion inherits exactly this definition.
The set is wider than space and tab, and wider still in some flavors, which matters when trimming input that arrived from a browser. A byte-order mark counting as whitespace surprises most people.
Being exact complements is what makes them composable, and it is also the reason that putting a shorthand beside its uppercase form inside a negated class yields a set nothing can satisfy.
Rejecting accented letters is the defect that ships in name and username validators and is only found by a user with such a name. A Unicode property escape is the replacement, not a longer ASCII range.
One pattern, two meanings, with no error in either language — which is why a regex moved between codebases should be re-tested rather than assumed. The difference appears only on non-ASCII input.
Having an explicit switch is better than relying on a default, because the default differs by language and by subject type. Setting it deliberately documents which behavior the pattern was written for.
E. Unicode mode, property escapes and set algebra
It does two unrelated things at once: it fixes how the subject is divided, and it makes previously-tolerated escapes into errors. The second is why adding it to an old pattern can break compilation.
Operating on code units rather than characters means a single emoji is two positions, so a wildcard matches half of one and a negated class can split it. The result is mojibake rather than an error.
Reading as a repetition count without the flag is the dangerous failure, because the pattern still compiles and matches something else entirely. Adding the flag is what makes the braces mean a codepoint.
Selecting by category is what replaces a hand-built range for anything non-ASCII, and it stays correct as Unicode grows. It needs the flag, and that requirement is why brittle hand-built ranges persist.
The broader form includes characters a script borrows but does not own, such as shared punctuation and digits. Choosing the narrow one is how a pattern for Greek text rejects the comma inside it.
The complement form is straightforward on its own; what changes is how it composes inside the newer set-operation classes, where intersection and difference give a second way to express the same exclusion.
Intersection and difference let a class say what it means directly — letters that are not Latin, say — instead of being built from a double negation nobody can read. Nesting is what makes that composable.
Matching a multi-codepoint sequence is new in kind rather than degree: every other escape consumes one unit. It is the only built-in answer to emoji that are genuinely several codepoints.
What a reader calls one character can be several codepoints, so counting, truncating or reversing by codepoint produces visible corruption. The absence of a grapheme escape is why a library is the answer here.
Full folding brings in pairs no one intends — a measurement symbol folding to an ordinary letter — so case-insensitive matching under Unicode admits more than a reader expects. It is a correctness issue in validators.
The engine compares codepoints, so two visually identical strings composed differently simply do not match. Normalizing the subject before matching is the fix, and it belongs outside the pattern.
F. Quantifiers and repetition
The difference between the first two is whether zero occurrences count, and that single choice decides whether a pattern accepts an empty field. Most over-permissive patterns chose the wrong one of the two.
The open-ended lower form exists and the open-ended upper one does not, so an attempt at the missing form is parsed as literal characters rather than rejected. That is why it fails quietly rather than loudly.
Binding to the single preceding unit is why a repetition that looks like it covers a word covers only its last letter. Adding a non-capturing group is the fix, and it costs nothing at match time.
Grouping before making something optional is the same mechanism as above, read in the other direction: without the brackets the question mark would apply to one character and change the meaning entirely.
Multiplication is the harmless reading; the dangerous one is that the same nesting, over an ambiguous inner atom, is exactly the shape that makes backtracking explode. The two cases differ only in ambiguity.
Only the final repetition survives, so a repeated capture cannot collect a list — which is what people reach for it to do. Matching the whole run and splitting it afterwards is the working approach.
Without the guard a quantified group that consumed nothing would repeat forever, so engines break the loop. Knowing it exists explains why a pattern built from optional parts terminates instead of hanging.
Repeating something that consumes no characters is either rejected or pointless, depending on the flavor. Meeting one usually means a group was intended and the brackets landed in the wrong place.
The ceilings differ by orders of magnitude between flavors, so a generated pattern with a large count can compile in one language and be refused in another. It is a portability limit rather than a practical one.
The walk-back is the part worth visualizing: the repetition runs to the end and then surrenders characters one at a time until the suffix fits, which is why it lands on the LAST one rather than the first.
G. Greediness, laziness and possessiveness
Taking as much as possible and then giving it back on demand is the behavior every surprising over-long match comes from. The giving-back is also where a backtracking engine spends its time.
Taking the minimum and growing only under pressure is the mirror image, and it is a local change — which is the next entry, and the thing people most often get wrong about it.
The modifier attaches to one quantifier rather than switching the pattern into a mode, so a pattern with several repetitions needs each one considered separately. Expecting it to be global produces half-fixed patterns.
The start position is still chosen first and leftmost, so laziness only shortens the match found at that start. It cannot find a shorter match further along, which is what people expect it to do.
The classic tag-matching pair, and both are usually the wrong tool — the greedy one spans the line and the lazy one backtracks its way to the first delimiter. The next entry is the better answer.
Excluding the delimiter means the repetition cannot run past it in the first place, so the right answer is reached with no backtracking at all. Faster and clearer than the lazy form, for the same result.
Refusing to give characters back is what turns a potential explosion into a fast failure, because the engine has no positions left to revisit. It changes which strings match, which is the next entry.
It is not merely an optimization: a possessive repetition that swallows the character a later part needs makes the whole pattern fail where the ordinary form succeeds. Use it where that cannot happen.
Absent from JavaScript, which is why the atomic-group emulation exists there. Checking availability before reaching for it saves writing a pattern that works in one of your two runtimes.
H. Anchors and input boundaries
Asserting a position rather than consuming a character is why they can sit next to each other and why they never appear in the matched text. Every boundary construct shares that property.
Turning them into line anchors is what the flag does and all it does — it leaves the wildcard alone, which is the pairing people assume and the source of a lot of confusion between the two flags.
Tolerating a final newline in some flavors and not others is why a validator ported between languages starts accepting input with a stray line ending. The absolute-end escape is the way to be explicit.
The two escapes are split differently across flavors, so the one that means strict end in one language means something laxer in another. Where the distinction matters, test it rather than recall it.
Ignoring the multiline flag is the point: it anchors to the subject rather than to a line, which is what you want for validating a whole value regardless of what flags are set.
Both anchors together is the portable way to say the entire string, and a dedicated whole-match API is clearer where the language has one. Omitting either is how a validator accepts a substring.
Alternation binds loosest, so an anchor written before a branch list applies only to the first branch. The brackets are doing real work here, and leaving them out produces a pattern that accepts far too much.
I. Word boundaries
Defined as a transition between a word character and anything else, including the edges of the subject — which is why it works at the start and end without a special case. Its definition is inherited, not independent.
Asserting that both sides are the SAME kind is the complement, and it is useful for finding something inside a word rather than at its edge. It fires in the middle of a run of letters or of symbols alike.
Because an apostrophe is not a word character, a contraction is two words to the engine. Every oddity of the boundary assertion is really an oddity of the word-character set it reads.
An accented letter outside the engine's word set puts a boundary in the middle of a word, so the assertion fails where a reader would say it should hold. The fix is the word definition, not the boundary.
Requiring whitespace on each side is stricter than requiring a word edge, so it misses the term beside a comma or at the end of a sentence. The boundary form is almost always the one meant.
The same escape means two unrelated things depending on whether it is inside brackets, and the inside meaning is a control character nobody wants. There is no way to assert a boundary within a class.
Wrapping the term is the standard fix for a replacement that corrupts longer words containing it. It is worth applying by default in any rename, because the failure is only visible on the words you did not think of.
J. Alternation and its ordering semantics
The first branch that lets the ENTIRE pattern succeed is taken, which is not the same as the first branch that matches. That distinction is what makes ordering a correctness concern rather than a style one.
Taking the first viable branch rather than the longest is the single most surprising consequence of the engine's model, and it means a list of alternatives must be ordered longest-first to behave as expected.
Binding loosest of all the operators is why a pattern mixing alternation with anchors or sequences almost always needs brackets. Reading one without them means mentally inserting the implied grouping.
The non-capturing form bounds the alternation without adding a slot the engine has to record. Reaching for the plain form by habit allocates captures nobody reads, which costs on every attempt.
An empty branch makes the whole group optional in a roundabout way, and it usually arrives by accident as a trailing separator. It is legal, so nothing reports it.
Branches are tried in written order, so putting the common or cheap case first is free performance. It matters most inside a repetition, where the ordering cost is paid on every iteration.
A set is one comparison where a branch list is several attempts with unwinding between them. Where every alternative is a single character, the set form is strictly better and the gap compounds under repetition.
K. Groups: capturing, non-capturing and atomic
Numbering by opening bracket rather than by closing one is what makes nested groups predictable: read left to right and count the openings. Inserting a group anywhere renumbers everything after it.
The outer group takes the lower number because its bracket opens first, which reads backwards to anyone expecting innermost-first. Counting openings left to right gives the right answer every time.
Reserving index zero for the whole match is why the first real capture is one, and it is the off-by-one that shows up when iterating a result array naively.
Grouping without allocating a slot is the right default whenever you only need scope, and the saving is real in a repeated pattern where the engine records a value on every iteration.
A group in a branch that was never taken yields a null-ish value rather than an empty string, so code that assumes a string crashes on the one input that took the other branch.
Half the brackets in a typical pattern exist to bind a repetition rather than to extract anything, which is why the non-capturing form deserves to be the habit.
Every slot is state the engine saves and restores as it explores, so unused captures are paid for on each attempt rather than once at the end. On a hot path the difference is measurable.
Discarding the backtracking positions inside it is what converts a potential explosion into a quick failure. It is a promise that the engine will never reconsider how that part matched.
Like the possessive form, it is not purely an optimization: if the discarded alternatives contained the one the rest of the pattern needed, the match fails where the ordinary group succeeds.
A lookahead that captures, followed by a reference to it, reproduces the behavior where the construct is unavailable — the assertion matches greedily and the reference locks the result in.
L. Named capturing groups
The two spellings differ by one character, which is enough to make a pattern fail to compile when moved between languages. It is the first thing to check when a ported pattern raises a syntax error.
A name does not remove the number, so the same group is reachable both ways and a replacement string can use either. That matters when mixing named groups with positional references.
The access path differs by language, and in JavaScript the container is absent entirely when the pattern has no named groups — so defensive code should check before destructuring it.
Surviving the insertion of another group is the practical reason to use names: positional references break silently when someone adds a bracket, and nothing in the tooling flags it.
Allowed only where the alternatives cannot both match, which is the condition that makes the name unambiguous. Outside that, reusing a name is a compile error rather than a last-one-wins.
The replacement syntax has its own spelling for a name, different again from the pattern's. Mixing the two produces a literal string in the output rather than an error.
M. Backreferences
Referring to the captured TEXT rather than re-running the sub-pattern is the whole distinction, and it is what lets a pattern require two occurrences to be identical. Re-running would match any two.
The dollar form belongs to replacement strings and means nothing inside a pattern, so writing it there produces a literal that matches a dollar sign. The two syntaxes are easy to swap and fail quietly.
Another place the two language families spell the same idea differently, and the reason a ported pattern with named groups needs checking in two spots rather than one.
Referring to a group that never participated fails in some engines and matches empty in others, so a pattern with optional captures behaves differently across languages on exactly the inputs that exercise it.
The comparison respects the flag, so two characters differing only in case count as the same text. That is usually desirable and occasionally the reason a duplicate-detector reports a false positive.
Needing to remember captured text is precisely what a linear-time engine cannot do, which is why the guaranteed-fast engines omit the feature rather than implementing it slowly.
A reference to a group that does not exist falls back to an octal character escape in legacy mode, so a pattern with a miscounted number matches a control character instead of failing.
N. Lookaround
Testing forward and then returning the cursor is what lets several conditions apply at one position. Nothing it inspects becomes part of the match, which is why it composes so freely.
Succeeding exactly when the inner pattern cannot match here is the building block for every not-followed-by rule, and the cursor is unmoved either way.
Testing the text immediately before the position completes the pair, and it is the newer of the two — which is why its support and its length restrictions vary so much between flavors.
Some engines allow an arbitrary-length look back, some bound it, and one rejects it outright. A pattern relying on it is the least portable thing in this chapter.
Groups inside an assertion keep their captured text even though the assertion consumed nothing, which is what makes the atomic emulation and the overlapping-match technique work.
Several assertions in a row all test from the same spot, so they read as independent requirements rather than as a sequence. That independence is what makes them a clean way to express a rule set.
Expressing must-contain requirements without constraining their order is the canonical use, and it scales: each additional requirement is one more assertion rather than a rewrite of the whole pattern.
Capturing inside an assertion finds matches that overlap, because nothing is consumed and the scan advances one position at a time. A consuming pattern skips past everything it matched.
One rejects a sequence at this position and the other rejects a single character, so they are not interchangeable even when they agree on simple input. The difference shows on multi-character exclusions.
The assertion scans the rest of the line before accepting it, which a negated class cannot do — a class excludes one character everywhere rather than one sequence anywhere. The two read similarly and differ completely.
Marking a position without consuming anything is what lets a replacement insert separators into a number, since each inserted character would otherwise shift the scan past the next group.
O. Flags and inline modifiers
Relaxing the comparison is simple on ASCII and much broader under Unicode, where full folding pulls in pairs a reader would not call equal. In a validator that widening is a correctness change.
It changes how the methods behave rather than what the pattern describes, which is why adding it can alter a result without altering a single match. Every entry in the state chapter follows from that.
It moves the two anchors and leaves the wildcard untouched, which is the pairing most people assume it changes. Wanting the wildcard to cross lines is a different flag entirely.
Letting the wildcard cross a line terminator is what makes a pattern work on a whole document rather than line by line. It is the flag people reach for the multiline one by mistake.
Requiring the match to begin exactly where the last one ended, rather than scanning forward, is what a tokenizer needs — a gap should be an error rather than something to skip over quietly.
Whitespace and comments are the difference between a pattern that can be reviewed and one that can only be replaced. Its absence in JavaScript is why long patterns there are built by concatenation.
Applying a flag to one span is what lets a single part of a pattern be case-insensitive without relaxing the rest, which an all-or-nothing flag cannot express.
Required at the very start in Python, where elsewhere it may appear mid-pattern. A modifier placed later is a warning or an error depending on version, which makes it a portability detail worth knowing.
The sets barely overlap between language families, so a flag string copied with a pattern is the second thing to check after the syntax. An unknown flag is usually a loud failure, which is the good case.
Exposing offsets for the match and every capture is what lets a caller slice the original string rather than re-finding the text, which matters for highlighting and for building a syntax tree.
P. The backtracking engine
Exploring depth-first and rewinding on failure is the one model that explains greediness, alternation order and the whole ReDoS chapter at once. Every surprising behavior here is this model being literal.
Two engine families answer the same pattern differently, which is why a pattern moved to a POSIX tool suddenly returns a longer match. Neither is wrong; the alternation ordering rules only hold for one of them.
Each choice is a place the engine can return to, and nesting them multiplies rather than adds. Counting the decision points in a pattern is how you estimate its worst case without running it.
Failing completely at one start means the whole pattern is tried again one character along, so a pattern that is slow to fail is slow once per position rather than once per subject.
Pre-scanning for a required literal lets the engine skip start positions it can prove cannot work, which is why a pattern beginning with a literal is often far faster than one beginning with a class.
A leading anchor removes the start-position loop entirely, turning many attempts into one. That is a correctness measure that happens to be the single largest performance win available.
Guaranteeing linear time is bought by dropping backreferences and lookaround, so the trade is explicit rather than hidden. For untrusted patterns that trade is usually the right one.
Caching which states have been tried at which positions converts the exponential case into a polynomial one, and whether an engine does it is the difference between a hang and a pause.
Backreferences and recursion take the tool beyond what the name claims, which is why some engines can offer linear time and others cannot. The name is historical rather than descriptive.
Rebuilding the same pattern on every iteration is the commonest performance mistake in this area, and it is invisible in profiles that only show the matching call. Hoisting it is a one-line fix.
Q. Catastrophic backtracking and ReDoS
A repetition wrapped around something that can itself split the same text many ways is the signature, and recognizing it by shape is faster than reasoning about any particular input.
The two degrade at very different rates, so a quadratic pattern is a latency problem and an exponential one is an outage. Both are worth fixing; only one needs fixing today.
A matching input returns quickly, so testing with valid data proves nothing about the attack. The dangerous string is the one that almost matches and fails at the very end.
Two ways to divide the same text is what multiplies the search, so the repair is always to make the division unique rather than to shorten the pattern or bound the input.
One crafted request occupying the CPU stops every other request on a single-threaded runtime, which turns a slow pattern into a denial of service rather than a slow endpoint.
Ensuring the alternatives inside the repetition cannot both match the same character is the structural fix, and it leaves the pattern's meaning intact while removing the ambiguity entirely.
Removing the backtracking positions removes the attack outright, where the flavor offers it. It changes which strings match, so it needs the same testing as any other change to the pattern.
A length cap and a match timeout are defense in depth rather than a fix, and they are what protects you from the pattern you have not audited yet. They belong around every untrusted match.
Escaping addresses a different attack entirely: it stops a user supplying a pattern, and does nothing about a pattern you wrote being slow on input the user supplies. Both defenses are needed.
R. Global and sticky state
The object remembers where it finished, so a pattern is stateful in a way a literal does not look. Everything confusing about reusing one traces back to this single property.
A module-level pattern with the global flag alternates between true and false on repeated tests of the same string, which reads as a bug in the caller rather than in the shared object.
Zeroing it, or building a fresh object, is the explicit fix, and the second is usually clearer. The failure it prevents is intermittent, which is what makes it expensive to debug.
A literal inside a loop produces a new object each iteration in modern engines, so it is safe but also recompiles each time. That is the trade against hoisting a shared, stateful one.
A manual loop over matches must advance past an empty match itself, or it re-finds the same empty match forever. The higher-level iterating APIs handle this, which is a reason to prefer them.
Failing rather than searching forward is exactly what a tokenizer wants, because unexpected input should stop the scan rather than be silently skipped over.
With both set the anchoring behavior wins, so the combination behaves as the stricter of the two rather than as a blend. Setting both deliberately is rare; inheriting both from a helper is not.
Keeping no state on the compiled pattern is why the whole class of alternating-result bugs does not exist there, and why the iterating call returns something independent each time it is used.
S. Match results and the matching API
Asking only whether something matched lets the engine skip recording captures, so it is the right call when the text is not wanted. It is also the one that carries the shared-state trap when the pattern is global.
Returning null rather than an empty array on failure is what makes the result unsafe to index without checking, and the extra properties on success are easy to miss because the value looks like a plain array.
The same call returns two different SHAPES depending on a flag: every matched string, or one rich result with captures. Code written against one form breaks silently when someone adds or removes the flag.
Keeping captures and offsets for every result is what the plain global form throws away, which makes it the right default for extraction. It requires the flag rather than assuming it, and throws otherwise.
Returning a position rather than text is the cheapest way to answer where, and its failure value is a number rather than null — so the check is a comparison rather than a truthiness test.
One anchors implicitly at the start and the other scans, and the names give no hint which is which. Reaching for the anchoring one by mistake is why a pattern seems not to find text that is plainly there.
Requiring the entire string without adding anchors is clearer and harder to get wrong, since an anchor can be forgotten or placed inside an alternation where it binds to one branch.
The return shape changes as soon as the pattern has groups, so adding a capture to an existing pattern silently changes what the caller receives. The iterating form keeps one shape throughout.
Offsets let the caller index back into the subject instead of searching it a second time, which is what highlighting and token streams need. JavaScript exposes the same information behind a flag.
Compiling once is about clarity as much as speed in languages with an internal cache, and about speed alone where there is none. Either way the hoisted form documents that the pattern is fixed.
T. Replacement syntax
Replacing only the first occurrence is the default, which is why a substitution that works on a test string with one match quietly under-replaces on real input.
Throwing when handed a non-global pattern is a deliberate guard against the ambiguity above, and it is one of the few places the API refuses rather than silently doing something defensible.
The replacement string has its own small language, and these are its variables. They are the reason a literal dollar in the output needs escaping, which is two entries down.
Inserting everything before or after the match is rarely wanted and is quadratic when it is, since each replacement copies the surrounding text. Worth knowing mainly so an accidental one is recognized.
Doubling is how a literal dollar survives, which matters when substituting currency or shell variables. Without it the parser reads the following character as a group reference.
A group that never participated contributes nothing rather than the string undefined, which is the sensible behavior and differs from what the same group yields when read from a match object.
Escapes that mean something in a pattern are ordinary characters here, so a backslash sequence copied from the pattern into the replacement produces a literal letter. The two languages only look alike.
The argument order is fixed and positional, with the named-groups object last and present only when the pattern has names. Destructuring it blindly is how a replacer breaks on a pattern change.
Computing each replacement is what removes an explicit find-and-splice loop, and it keeps the offsets correct automatically — which is the part a hand-written loop usually gets wrong.
The counting variant returns how many replacements happened, which is the cheapest way to assert that a migration touched what you expected rather than hoping it did.
U. Splitting
Splitting on a pattern rather than a fixed string is what lets the separator absorb surrounding whitespace, so the pieces come back trimmed without a second pass.
A capture inside the separator is interleaved into the result, which doubles the array length and shifts every index. Switching to a non-capturing group is usually the fix rather than adjusting the indices.
One language truncates the array and the other leaves the unsplit remainder as the final element, so the same limit means two different things. Porting a split needs this checked explicitly.
Splitting between every unit is occasionally what you want, and in JavaScript it divides by code unit rather than by character — which cuts an emoji in half unless the Unicode flag is set.
A separator at either end yields an empty element, which is correct and almost never wanted. Filtering afterwards is the usual answer, and knowing to expect it is the real content.
Splitting on a zero-width match was an error before Python 3.7 and works after, so a pattern that behaves differently across versions may be relying on exactly this change.
A quoted comma means the separator is context-dependent, which is precisely the decision a regex cannot make. That is the clearest example of the parser rule in the last chapter.
V. Python's re module in particular
The raw prefix stops the language's own escaping from consuming backslashes before the pattern ever sees them, which removes the doubling that otherwise affects every escape.
The two cannot be mixed, and a bytes pattern keeps ASCII semantics for its shorthands. That makes the subject's type a decision about Unicode behavior rather than just about encoding.
Because the module-level calls cache compiled patterns, compiling explicitly is about naming and reuse rather than about speed — which inverts the advice that applies in languages without a cache.
Each constant has an inline equivalent, so a flag can live with the pattern rather than at the call site. Putting it in the pattern is what keeps the two from being separated during a refactor.
One yields match objects with positions and the other bare strings or tuples, so the choice decides whether offsets are available later. The richer one costs nothing extra to iterate.
Conditionals and, since 3.11, possessive quantifiers are in the standard module; recursion and variable-length lookbehind are not, and a lookbehind there must match a fixed number of characters. Knowing which features are absent saves debugging a syntax error.
Binding and testing in one expression is the idiom because the alternative invites using a match object that may be None. It reads oddly once and then removes a whole class of mistake.
Named alternatives plus an iterating scan is a workable lexer, and it gets the positions for free. Its limit is that the alternation order becomes the precedence rule, which must be deliberate.
Printing the parsed structure is what localizes a precedence mistake, which is otherwise invisible: the pattern compiles and simply groups differently from how it reads.
Carrying the position of the offending construct turns a syntax error from a guess into a pointer, which matters most in a long generated pattern where reading it by eye is impractical.
Applying replacement syntax to a match already in hand is what lets the substitution happen away from the scanning call, which is useful when the decision to replace is made by surrounding code.
W. PCRE-only constructs
Re-running a group's pattern without repeating its text is what keeps a long pattern maintainable, since the definition lives in one place. It is a call rather than a backreference, so it matches the PATTERN again.
Matching balanced nesting is the capability that puts the tool outside regular languages altogether, and it is the honest answer to the cases where people reach for a regex and should not.
Branching on whether a group participated lets one pattern require a closing delimiter only when an opening one was present, which otherwise needs two patterns and a decision outside them.
Discarding everything matched so far from the REPORTED match is a lookbehind substitute without the length restrictions, and it is why a replacement can leave a prefix untouched.
Anchoring to where the previous match ended is what makes a scan contiguous, so a gap stops the sequence rather than being skipped. It is the same intent as the sticky flag elsewhere.
Steering the backtracker explicitly is powerful and hard to reason about, which is why they appear mostly in generated or heavily tuned patterns. They can change results, not just speed.
Giving each alternative the same group numbers is what lets a pattern with several shapes be read with one set of indices, instead of numbering every branch separately and checking which is set.
The Unicode behavior of the shorthands and the boundary is opt-in through two separate options, so a build with one and not the other gives half the expected behavior on non-ASCII input.
Built-in ceilings are what turn a runaway pattern into an error instead of a hang, which is the defense a library can offer that a pattern author may forget. They are configurable and worth setting deliberately.
X. Using regex well, and when not to
A reviewable pattern is one that can be changed safely later, and a wall of symbols is one that gets replaced rather than edited. Where the flag is unavailable, building the pattern from named parts is the substitute.
Two passes that each do one thing can be read and tested separately, where a single pattern covering both can only be verified as a whole. The combined one is also where the ambiguity that causes blowups hides.
Without escaping, the user supplies part of the pattern, which is both a correctness bug and a performance attack. It is the one rule in this chapter that is a security control rather than a style preference.
A pattern is defined as much by what it rejects as by what it accepts, and only the rejections are ever under-tested. Writing the counter-examples first is what surfaces a missing anchor.
Nesting and context-dependent quoting are decisions that need state the engine does not keep, so a pattern can only ever handle the subset you have seen. The failures arrive as silently wrong extraction.
The full grammar admits addresses nobody sends mail to, so matching it exactly is effort spent in the wrong place. Accepting generously and confirming by sending is the design that actually verifies the address.
The edge cases in these formats are exactly the ones a pattern gets wrong, and they are already handled. Reaching for a parser is not an admission of defeat; it is choosing a tool that keeps state.
A prefix test or a plain split is clearer to the next reader and faster besides, and it cannot be given a pathological input. Reaching for a pattern by habit is where much accidental complexity comes from.
Naming the pattern, stating what it must reject, and pinning an example of each is what makes it safe to change later. Without that, the only safe operation on a non-trivial pattern is leaving it alone.
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.