Start your 3-day free trial
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.


To write regular expressions with AI, name the engine and contract, then test it. Follow How to Use AI; the real engine decides whether it works.
Key Takeaways
- Name the runtime, engine, version, API, flags, and full-match or search semantics.
- Translate the requirement into an allowlist-style contract before writing syntax.
- Test accepted, rejected, boundary, Unicode, newline, and adversarial input.
- Keep regex syntax, programming-language string escaping, and shell escaping separate.
- Verify captures and replacements, not only a true/false match.
- Test performance with bounded input and watch for catastrophic backtracking.
- Prefer a parser or ordinary string operations when regex becomes opaque.
A regex cannot repair an ambiguous requirement. Write what is allowed, what is forbidden, whether the entire value must match, and what later code will do with captures.
For a synthetic ticket key, the contract might be:
| Field | Decision |
|---|---|
| Accepted shape | Two uppercase ASCII letters, hyphen, four digits |
| Whole value? | Yes; no leading or trailing text |
| Length | Exactly 7 characters |
| Accepted examples | AB-0042, ZX-9000 |
| Rejected examples | ab-0042, AB 0042, AB-42, XAB-0042Y |
| Captures | Prefix and numeric part |
| Input origin | Untrusted form field |
| Maximum input length | 64 characters before matching |
OWASP recommends defining allowed characters and minimum and maximum lengths as part of input validation.[4] Prefer a narrow allowlist when the domain permits it. Do not ask for “a perfect regex for any identifier”; name the actual grammar and policy owner.
Keep validation and extraction distinct. A pattern that finds a ticket inside a paragraph should not automatically validate a complete form value. Likewise, client-side convenience validation does not replace server-side enforcement.
Regex syntax and behavior differ across JavaScript, Python, Java, .NET, Rust crates, database functions, command-line tools, and editors. Even shared syntax may use different APIs or defaults.
Record:
| Field | Example |
|---|---|
| Language/runtime | Python 3.14 |
| Engine/library | Standard re module |
| API | re.fullmatch |
| Pattern representation | Raw string literal |
| Flags | None |
| Unicode policy | ASCII letters only by contract |
| Captures | Named prefix and number |
| Replacement behavior | Not used |
MDN documents that JavaScript flags change behaviors such as global search, case-insensitive matching, multiline anchors, dot matching newlines, and Unicode handling.[1] Python's re module has its own syntax, flags, matching functions, and replacement backreferences.[2] “It worked in an online tester” is not useful evidence unless the tester uses the same engine and settings.
The choice among full match, prefix match, and search is part of the contract. In JavaScript, explicit ^ and $ anchors are often used for full-value validation, while in Python fullmatch expresses that intent at the API level. Check newline and multiline semantics rather than assuming anchors mean the same thing everywhere.
Create cases that a reviewer can approve without reading regex syntax. Start with examples from the requirement, then add boundaries and near misses.
| Case | Input | Expected | Reason |
|---|---|---|---|
P-01 | AB-0042 | Match; AB, 0042 | Typical valid value |
P-02 | ZX-9000 | Match; ZX, 9000 | Different valid values |
N-01 | ab-0042 | No match | Lowercase forbidden |
N-02 | AB 0042 | No match | Wrong separator |
N-03 | AB-42 | No match | Too few digits |
N-04 | XAB-0042Y | No match | Extra surrounding text |
B-01 | Empty string | No match | Minimum boundary |
B-02 | 64-character nonmatch | No match within budget | Maximum accepted input size |
A-01 | Long repeated prefix plus ! | No match within budget | Backtracking challenge |
Do not let AI create both the rule and every expected result without review. Derive the oracle from the product requirement, protocol, standard, or policy. The test-generation workflow can broaden the matrix, but the owner approves the labels.
Add cases for empty input, one character below and above each limit, repeated delimiters, embedded newlines, confusable Unicode, leading and trailing whitespace, null handling at the call site, and extremely long rejected values. When captures feed another operation, state their expected values explicitly.
Give the model the contract and test table, not private production samples.
Target: Python 3.14 standard re, using re.fullmatch and a raw string.
Contract: exactly two uppercase ASCII letters, a literal hyphen, four digits.
Return one pattern with named groups prefix and number.
Explain every token and list assumptions. Do not broaden accepted characters.
Do not invent extra requirements. Include runnable tests for every supplied case.
Flag any engine or escaping uncertainty as [VERIFY].
A reasonable candidate is:
pattern = r"(?P<prefix>[A-Z]{2})-(?P<number>[0-9]{4})"
Because the call site uses re.fullmatch, explicit anchors are unnecessary for this exact example. That statement is engine- and API-specific, not a universal style rule. Ask why every group, quantifier, class, alternation, lookaround, and flag exists. Remove syntax that has no contract purpose.
Many broken patterns are correct regex wrapped in the wrong string. Track these layers separately:
For Python, a raw string often makes a regex easier to read, but it still follows Python raw-string rules. In JavaScript, a regex literal and new RegExp("...") have different string-escaping needs. In a replacement string, backreferences may use yet another syntax; Python documents named and numbered references such as \g<name> and \g<number>.[2]
Store the exact source literal, the runtime pattern value if inspectable, and the call that executes it. Test a literal backslash, quote, newline, and replacement capture where relevant. Do not “fix” escaping by trial and error without explaining which layer changed.
Run the corpus in the real project test framework. A small synthetic Python example is:
import re
PATTERN = re.compile(r"(?P<prefix>[A-Z]{2})-(?P<number>[0-9]{4})")
def parse_ticket(value: str):
if len(value) > 64:
return None
return PATTERN.fullmatch(value)
assert parse_ticket("AB-0042").groupdict() == {
"prefix": "AB",
"number": "0042",
}
assert parse_ticket("ab-0042") is None
assert parse_ticket("XAB-0042Y") is None
The length check bounds work before matching; its exact limit comes from the synthetic contract. In a real application, validate types and nulls before calling the engine. Run server-side tests even if a browser also checks input.
If the regex transforms text, assert the entire replacement output and unmatched portions. If it runs repeatedly with a global or stateful API, test iteration and empty matches. If it processes multiple lines, cover both line endings and the chosen multiline behavior.
Character classes can hide policy decisions. Does \d mean ASCII digits or a wider Unicode category in your engine and mode? Does case-insensitive matching fold characters beyond A-Z? Does . include a newline? Does a global flag preserve state between calls?
Build a review table:
| Dimension | Question | Evidence |
|---|---|---|
| Unicode | Which scripts and normalization forms are valid? | Approved corpus |
| Whitespace | Are spaces, tabs, and newlines distinct? | Boundary tests |
| Flags | Why is each flag required? | Runtime configuration |
| Captures | Are names and optional values stable? | Exact assertions |
| Replacement | How are missing groups handled? | Output fixtures |
| Iteration | Can an empty match stall a loop? | Loop test |
Never silently use ASCII-only syntax for international names, or Unicode-wide syntax for a protocol that requires ASCII. The product or protocol owner decides; the model implements the recorded policy.
Some backtracking engines can take dramatically longer on crafted input. OWASP highlights risky structures such as repetition inside repeated groups or overlapping alternation and gives examples including (a+)+$.[3] This is why a regex test suite needs adversarial nonmatches, not only normal examples.
Review for nested ambiguous quantifiers, overlapping alternatives, backreferences over uncontrolled text, and user-supplied pattern fragments. Then:
Do not publish a universal “safe in 10 ms” number. The budget belongs to the application and environment. A model's assurance that a pattern is linear is not a measurement or proof.
Never concatenate raw user input into a regex. If the intent is literal search, use the engine's escaping function and still apply input bounds. If users are intentionally allowed to supply patterns, treat that as a separate high-risk feature with engine isolation, resource limits, and security review.
Choose ordinary string operations, a parser, or a domain library when the grammar is nested, context-sensitive, or maintained by a standard. HTML, programming languages, complex addresses, and evolving business rules often exceed what a readable pattern should own.
Stop when:
If the pattern fails, use the AI debugging process to reduce a concrete mismatch. For generated code around it, apply the pre-run review checklist.
One example cannot define all accepted and rejected input. Provide a written contract plus positive, negative, boundary, and adversarial cases, then execute them in the target engine.
The tester may use a different engine, flags, match API, or escaping layer. Reproduce the exact runtime pattern and call site in your project test.
Use the idiom that accurately expresses whole-value matching in your target engine. Test embedded newlines and multiline flags because anchor behavior can vary.
Regex escaping defines pattern syntax; string escaping defines how source code stores that pattern. JSON, YAML, templates, and shells can add further encoding layers.
\d always the same as [0-9]?No. Depending on the engine and mode, \d may match a wider Unicode digit set. Choose the class that implements the recorded input policy and test representative characters.
Review its structure, bound input, add long crafted nonmatches, and measure it in the real engine under controlled resource limits. Do not rely on the model's complexity claim alone.
Do not concatenate it as pattern syntax. Use a literal-escaping function for literal search, or design an isolated, resource-bounded feature if user-defined patterns are truly required.
Use a parser when data is nested, context-sensitive, standardized, or hard to explain with a small pattern. Readability and maintainability are part of correctness.
Sources checked 4 September 2026.
Sign up to experience all premium features at no cost.
*Available only to new users. Each user is limited to one trial.