A support engineer needs to find ticket references in several thousand lines of text. Searching for one known reference works, but the engineer does not know all the numbers in advance. What remains consistent is their form: the letters TCK, a hyphen, and six digits.
A regular expression describes that form. It combines literal text with rules for choices, repetition, and positions, allowing a program to find strings that meet those rules. The surrounding code decides whether to report a match, extract part of it, replace it, or use the result as one condition for accepting input.
The examples here use JavaScript regular expressions. Naming the language matters because syntax, character classes, flags, and matching APIs vary between implementations. A pattern copied from another language should be checked in the engine that will execute it.
Begin with the difference between text and a pattern
An exact search for TCK-104822 identifies that particular sequence. A pattern can describe a larger family:
const ticket = /TCK-[0-9]{6}/;
console.log(ticket.test("Please check TCK-104822 today")); // true
The letters and hyphen match themselves. The character class [0-9] permits one ASCII digit, and {6} requires six repetitions of that class. The slashes delimit the JavaScript regex literal; they are not characters the input must contain.
This expression searches within a string. It can match part of TCK-1048229, leaving the final digit outside the match. That result is consistent with the pattern even if it is unsuitable for the engineer’s task. Regex does not automatically know where the application’s identifiers begin or end.
Mozilla’s JavaScript regular expression guide describes literals, construction, and matching operations. It is useful to read those operations alongside the syntax: a pattern and the code that applies it jointly determine the result.
Combine small rules to describe a value
A character class describes alternatives at one position. [ABC] accepts A, B, or C; it does not require the sequence ABC. A range such as [a-z] denotes a particular range of characters rather than every lowercase letter used across languages.
Quantifiers apply to the preceding item. A + requires one or more occurrences, * permits zero or more, and ? permits zero or one. A counted quantifier such as {2,5} provides a lower and upper bound. Grouping lets a quantifier apply to several characters together: (?:ab)+ describes ab, abab, and further repetitions.
For a small internal code, these pieces can be combined deliberately:
const code = /^[A-Z]{3}-[0-9]{2,5}$/;
console.log(code.test("ORD-42")); // true
console.log(code.test("ORD-4")); // false
This application’s rule requires three ASCII uppercase letters and between two and five digits. It does not establish that the prefix is a recognised business category. If only ORD and BUG are permitted, that is a further restriction to express through alternatives or a separate check.
Alternation uses | to separate choices. Group it carefully when combining it with other requirements. ^(?:ORD|BUG)-[0-9]{2,5}$ applies the same prefix and suffix boundaries to both branches. Leaving out the grouping can change which parts the boundaries constrain.
Boundaries distinguish searching from checking a field
In JavaScript, without the multiline flag, ^ and $ constrain a match to the beginning and end of the input. With multiline enabled, they also recognise line boundaries. The input boundary documentation explains that difference.
For a field that must contain only a ticket reference, an appropriate pattern is:
const ticketField = /^TCK-[0-9]{6}$/;
console.log(ticketField.test("TCK-104822")); // true
console.log(ticketField.test("Ticket: TCK-104822")); // false
Keep multiline disabled for this whole-field requirement. Also decide whether surrounding whitespace should be rejected or deliberately removed before checking. Calling trim() changes what the application accepts; it is a product decision rather than an automatic correction to the regex.
A word boundary, written \b, is different from an input boundary. It depends on the engine’s definition of word characters and is not a general definition of an identifier or a word in every language. An application whose identifiers allow hyphens or non-Latin characters may need more specific surrounding rules.
These distinctions become easier to maintain when the requirement is written in ordinary language next to representative examples. The worked process in using a regex builder develops that practice further.
Captures expose the part the application needs
Finding a reference and extracting its numeric portion are different operations. Parentheses can capture part of the match for subsequent code:
const found = /TCK-([0-9]{6})/.exec("Ticket TCK-104822 is open");
console.log(found?.[0]); // TCK-104822
console.log(found?.[1]); // 104822
The first entry is the complete matched text. The next entry contains the captured digits. If there is no match, exec() returns null, which the surrounding code must handle. Named captures can make more elaborate extraction easier to follow, while non-capturing groups organise the expression without adding result fields.
Keep identifiers as strings when their textual representation matters. Converting the captured digits to a number removes leading zeros. Whether that is correct depends on the identifier’s meaning, not on the fact that its characters happen to be digits.
Replacement uses matches in another way. JavaScript can insert a capture into replacement text:
const changed = "TCK-104822, TCK-000017".replace(
/TCK-([0-9]{6})/g,
"ticket:$1"
);
console.log(changed); // ticket:104822, ticket:000017
The global flag makes this replacement process find all applicable matches. Replacement syntax belongs to the host API, so a replacement string from an editor or another language may require adaptation even when the search pattern looks identical.
Repetition controls how much text is selected
Repetition has a matching policy as well as a count. JavaScript quantifiers are greedy by default: they try a larger permitted match before shorter alternatives. Adding a question mark makes a quantifier lazy, changing that preference. Mozilla’s quantifier reference describes the distinction.
For a controlled fragment such as start A end start B end, start.*end can select the whole fragment, while start.*?end can stop at the first compatible end marker. Here, the dot matches a character other than a line terminator because dotAll mode is not enabled. Neither expression understands a nested document. The change affects match selection under the stated pattern; it does not make a general document reader.
Prefer explicit boundaries where the format provides them. If a field ends at a comma and commas are forbidden within it, a class excluding commas expresses that rule directly. If quotes can contain commas, the input requires a different interpretation. The right expression depends on the format the application has agreed to accept.
Flags and character rules are part of the expression
A pattern’s behaviour includes its flags. Case-insensitive matching, multiline boundaries, dot matching across line terminators, and Unicode-aware interpretation each address different questions. Record the flags with the pattern rather than treating them as an incidental setting in a testing tool.
JavaScript’s shorthand \d means ASCII digits, while \w is not a general category for letters in every writing system. Mozilla’s character class reference documents those meanings. If international names or identifiers are involved, define the required character policy before selecting shorthand classes.
JavaScript regex objects using global or sticky matching also maintain lastIndex. Reusing one such object for independent validation calls can therefore produce unexpected results as the starting position changes. The lastIndex reference describes this state. A simple boolean field check usually has no need for global matching.
The distinction between a regex literal and a string containing regex source matters too. String construction introduces another escaping layer. Compare /[0-9]+/ with new RegExp("[0-9]+"): they describe the same pattern here, but source containing backslashes must also survive the string literal’s rules.
Decide what the match actually establishes
A match establishes the textual condition described by the expression under the selected engine and operation. That can be useful without being a complete validation decision.
| Task | What regex can establish | What needs other evidence |
|---|---|---|
| Ticket field | Required prefix, characters, and length | Ticket exists and caller may access it |
| Date-shaped text | Digits and separators in the required positions | Calendar validity and intended timezone |
| Simple import field | Agreed local format | Meaning in the complete imported document |
| Text replacement | Locations matching a pattern | Whether every replacement preserves meaning |
For external JSON, first parse the document and validate its representation through an appropriate boundary, as described in JSON Schema and TypeScript types. Apply a field pattern to the relevant string rather than searching the raw document for whichever value happens to appear first.
A final consideration is execution cost. Engines use different strategies, and ambiguous repetition can make some backtracking implementations spend substantial time on a near-match. RE2’s project documentation describes a different design with linear matching time for supported expressions and deliberate feature restrictions. There is no universal performance guarantee for every engine called regex.
For a production pattern, keep tests for accepted values, rejected values, surrounding text, and realistic input sizes. If the task grows into interpreting nested documents or making structural changes, revisit regex versus parsing. Understanding what the operation establishes is the foundation for choosing an expression that stays useful as the application changes.





