Alternation: precedence and ordered choice
| splits the whole pattern, or the whole group it sits in, into alternatives. The first branch that allows the overall match to succeed wins, so order matters and it is not longest match.
At a glance
- Syntax
- |
- Precedence
- the lowest of any operator, it splits the entire pattern or group
- Order
- the first branch that permits an overall match wins, regardless of length
- Empty branch
- (a|) is legal and matches the empty string
- For single characters
- [abc] is faster and clearer than (?:a|b|c)
- Anchors
- put ^ and $ outside the group, not inside one branch
In detail
Low precedence is the first surprise. /^cat|dog$/ does not mean a string that is either cat or dog; it means either a string starting with cat, or a string ending with dog. The alternation splits the entire pattern, anchors included, so the string catdog passes. Wrapping the alternatives in a non capturing group and putting the anchors outside is the fix.
Ordered choice is the second. Regular expression alternation is not longest match: the engine tries the branches left to right and takes the first one that lets the whole pattern succeed. Matching /a|ab/ against "ab" returns just a, and /ab|a/ against the same string returns ab. When one alternative is a prefix of another, list the longer one first.
Keyword lists are where this costs people real time. Scanning "input in int" with /(?:in|input|int)/g returns in three times, because the first branch always succeeds and nothing in the pattern forces a longer match. The engine does backtrack into later branches when the rest of the pattern fails, so adding \b rescues that particular case, but sorting the list longest first is the fix that works whether or not there is anything after the group.
When every branch is a single character, a character class is the better tool. [abc] does the same job as (?:a|b|c) with no alternation machinery, no risk of a stray capture, and less work for the engine. Save the pipe for alternatives that are genuinely longer than one character.
Examples
/^cat|dog$/.test("catdog"); // true, the pattern is (^cat) or (dog$)
/^(?:cat|dog)$/.test("catdog"); // false
/^(?:cat|dog)$/.test("dog"); // true/a|ab/.exec("ab")[0]; // "a"
/ab|a/.exec("ab")[0]; // "ab"
"input in int".match(/(?:in|input|int)/g); // ["in", "in", "in"]
"input in int".match(/(?:input|int|in)/g); // ["input", "in", "int"]List the longer alternative first whenever one is a prefix of another.
Things that catch people out
- Anchors inside an alternation apply to one branch only. /^cat|dog$/ accepts a great deal more than it looks like it does.
- Alternation is ordered, not longest match, so a shorter branch listed first will shadow a longer one that starts with the same characters.
- An accidental empty branch, usually a trailing pipe before the closing parenthesis, makes the whole group optional and the pattern then matches almost anywhere.
- Overlapping branches inside a quantified group are one of the standard catastrophic backtracking shapes.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →(?:...): grouping without creating a capture
A non capturing group applies quantifiers and alternation without taking a number, which is what keeps $1 pointing at the same thing after an edit.
[abc]: character sets and what loses its meaning inside
A bracket set matches one character from a list. Most metacharacters go literal inside it, and only four keep any special meaning at all.
Catastrophic backtracking: why a regex freezes a page
Nested quantifiers over overlapping characters make the work grow exponentially with input length. Here is the shape that causes it and the rewrites for it.
\n: matching a newline, and the double escape trap
\n matches the line feed character. The trap is building a pattern from a string, where the number of backslashes decides what the engine actually sees.
\t: matching tabs in TSV and indented text
\t matches the horizontal tab. Splitting a tab separated row on \s+ instead destroys every empty field, and JavaScript has no \h shorthand.
\r: carriage returns, CRLF and stray line endings
\r matches the carriage return that Windows text puts before every line feed. It is also a line terminator for ^ and $, which not every engine agrees with.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links