{
    "slug": "regex_conditional_pattern",
    "term": "Regex Conditional Patterns",
    "category": "regex",
    "difficulty": "advanced",
    "short": "Conditional regex constructs like (?(1)yes|no) match different subpatterns depending on whether an earlier group captured.",
    "long": "Conditional patterns let a regex choose between two alternatives based on whether a capturing group, named group, or lookaround assertion has already matched. The syntax in PCRE (and therefore PHP's preg_* functions) is (?(condition)yes-pattern|no-pattern). The condition is usually a group reference: (?(1)...) tests whether capture group 1 participated in the match, and (?(<name>)...) or (?(name)...) tests a named group. The no-pattern branch is optional, so (?(1)yes-pattern) matches yes-pattern only if group 1 succeeded and matches empty otherwise. Conditions can also be assertions: (?(?=lookahead)yes|no) selects a branch based on a zero-width test rather than a prior capture.\n\nThe classic use case is balancing optional delimiters. Consider matching an optionally bracketed value: (\\()?\\d+(?(1)\\)) matches '42' or '(42)' but rejects '(42' and '42)' because the closing paren is required if and only if the opening paren matched. Without a conditional you would write two separate alternatives and risk them drifting apart. Conditionals keep the dependency explicit and in one place.\n\nThese constructs are powerful but easy to misread. The group number inside (?(1)...) refers to the capture index, not a backreference to the captured text - that is a common source of confusion. Named conditions improve readability: (?(quote)...) is clearer than (?(2)...) when groups are renumbered during edits. Conditionals are supported by PCRE, .NET, and Python's re module, but not by JavaScript's native RegExp engine, so patterns relying on them are not portable to browser code without a library.\n\nBecause conditionals branch on prior match state, they can interact with backtracking in surprising ways. Keep the yes and no branches as specific as possible and anchor where you can to avoid catastrophic backtracking. Always test conditional patterns against inputs that exercise both branches plus the edge case where the condition group is optional and absent.",
    "aliases": [
        "conditional subpattern",
        "if-then-else regex",
        "(?(1)yes|no)"
    ],
    "tags": [
        "regex",
        "php",
        "conditional-subpattern",
        "pcre",
        "pattern-matching"
    ],
    "misconception": "People assume (?(1)...) backreferences the text captured by group 1; it actually tests only whether group 1 participated in the match, regardless of what it captured.",
    "why_it_matters": "Conditional patterns express delimiter-balancing and optional-dependency rules in one place, preventing the drift and duplication that two parallel alternatives invite.",
    "common_mistakes": [
        "Confusing the group reference in (?(1)...) with a backreference to the captured text rather than a participation test.",
        "Relying on conditional patterns in JavaScript, whose native RegExp engine does not support them.",
        "Renumbering groups during edits so (?(2)...) silently points at the wrong condition group.",
        "Forgetting the no-pattern branch is optional, then matching empty unexpectedly when the condition fails.",
        "Writing loose yes/no branches that trigger catastrophic backtracking on adversarial input."
    ],
    "when_to_use": [
        "An element must appear if and only if an earlier optional group matched, such as balanced delimiters.",
        "You need to avoid duplicating a shared subpattern across two parallel alternatives.",
        "The condition is naturally expressed as 'did this group participate' rather than a value comparison."
    ],
    "avoid_when": [
        "Targeting JavaScript's native RegExp engine, which does not support conditional subpatterns.",
        "A simple alternation or optional group expresses the same rule with clearer intent.",
        "The pattern is maintained by people unfamiliar with PCRE conditionals, raising the risk of misedits."
    ],
    "related": [
        "regex_capture_groups",
        "regex_named_groups",
        "regex_lookahead",
        "regex_pcre_php"
    ],
    "prerequisites": [
        "regex_capture_groups",
        "regex_named_groups",
        "regex_pcre_php"
    ],
    "refs": [
        "https://www.php.net/manual/en/regexp.reference.conditional.php",
        "https://www.pcre.org/current/doc/html/pcre2pattern.html",
        "https://docs.python.org/3/library/re.html"
    ],
    "bad_code": "// Two parallel alternatives that can drift apart and accept unbalanced input:\n$pattern = '/^(?:\\(\\d+\\)|\\d+)$/';\n\nvar_dump(preg_match($pattern, '(42)')); // 1 ok\nvar_dump(preg_match($pattern, '42'));   // 1 ok\n\n// But maintaining two copies of the \\d+ body invites mismatched edits,\n// and a naive single alternation accepts unbalanced forms:\n$loose = '/^\\(?\\d+\\)?$/';\nvar_dump(preg_match($loose, '(42')); // 1 - WRONG, missing close paren accepted\nvar_dump(preg_match($loose, '42)')); // 1 - WRONG, stray close paren accepted",
    "good_code": "// Conditional ties the closing paren to whether the opening one matched:\n$pattern = '/^(\\()?\\d+(?(1)\\))$/';\n\nvar_dump(preg_match($pattern, '(42)')); // 1 - both parens present\nvar_dump(preg_match($pattern, '42'));   // 1 - neither paren\nvar_dump(preg_match($pattern, '(42'));  // 0 - open without close rejected\nvar_dump(preg_match($pattern, '42)'));  // 0 - close without open rejected\n\n// Named condition reads better and survives group renumbering:\n$named = '/^(?<open>\\()?\\d+(?(open)\\))$/';\nvar_dump(preg_match($named, '(42)')); // 1",
    "quick_fix": "Replace duplicated parallel alternatives with (?(group)yes|no) to bind an optional element to whether its trigger group matched, then test both branches.",
    "severity": "low",
    "effort": "medium",
    "created": "2026-06-02",
    "updated": "2026-06-02",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/regex_conditional_pattern",
        "html_url": "https://codeclaritylab.com/glossary/regex_conditional_pattern",
        "json_url": "https://codeclaritylab.com/glossary/regex_conditional_pattern.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Regex Conditional Patterns](https://codeclaritylab.com/glossary/regex_conditional_pattern) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/regex_conditional_pattern"
            }
        }
    }
}