PLAINFINCH learn by building

Patterns with regular expressions

You will write patterns that check whether text has the right shape, such as an order code.

What is a regular expression?

A regular expression, or regex, is a small pattern that describes the shape of some text. Instead of asking "is this text exactly BK-1234?", you ask "is this two capital letters, a dash, and four digits?". JavaScript has regex built in. You write a pattern between two slashes, like /rye/, and then use it with string methods or the pattern's own test method.

Regex is compact, which is both its strength and its danger. One line can replace twenty lines of character checks, and the same line can be hard to read a week later. Code from AI tools often contains long patterns with no explanation. This lesson gives you enough of the language to read the common ones, write simple ones yourself, and recognise when a pattern is the wrong tool.

FigureThe order-code pattern /^[A-Z]{2}-\d{4}$/ split into start, two capitals, a dash, four digits and end, lined up with BK-1234. Below, BK-1234 and RY-0042 pass while BK-12345, bk-1234 and order BK-1234 fail.The order-code pattern /^[A-Z]{2}-\d{4}$/ split into start, two capitals, a dash, four digits and end, lined up with BK-1234. Below, BK-1234 and RY-0042 pass while BK-12345, bk-1234 and order BK-1234 fail.
Each piece of the pattern matches one part of BK-1234. The anchors ^ and $ are what reject BK-12345 and order BK-1234, which the old /BK/ let through.

How do test, match and replace work?

pattern.test(text) answers yes or no: does the text contain a match anywhere? That is the method the demo uses. text.match(pattern) returns details of the first match, or null if there is none, so always allow for null before reading from the result. text.replace(pattern, replacement) returns a new string with the first match swapped out. Strings in JavaScript never change in place, so keep the result.

Each of these works on the first match unless you add the g flag, which stands for global. With g, match returns an array of every matching piece of text, and replace swaps every match. There is also replaceAll, which needs either a plain string or a pattern with g.

const note = "2 rye, 1 bap, 3 rye";
console.log(/bap/.test(note)); // true
console.log(note.match(/\d+ rye/g)); // ["2 rye", "3 rye"]
console.log(note.replace(/rye/g, "sourdough"));

What do character classes and quantifiers mean?

A character class matches one character from a set. Square brackets list the choices: [aeiou] is any one vowel, [A-Z] is any one capital letter, [0-9] is any one digit. A caret inside the brackets flips the meaning, so [^0-9] is any one character that is not a digit. Shortcuts cover the common sets: \d is a digit, \w is a letter, digit or underscore, and \s is whitespace such as a space or tab. A dot on its own matches any single character, which is why a real full stop has to be written \..

Quantifiers say how many times the thing before them repeats. + means one or more, * means zero or more, and ? means zero or one, which makes something optional. Curly braces give exact numbers: {4} is exactly four, {2,5} is between two and five. So \d{4} is four digits, and colou?r matches both spellings of colour.

  • \d+ one or more digits, such as 7 or 2026.
  • [A-Z]{2} exactly two capital letters.
  • \s* any amount of whitespace, including none.
  • £\d+\.\d\d a price such as £12.50.

Why do anchors matter for validation?

By default a pattern can match anywhere inside the text. The demo's starting pattern /BK/ passes "BK-12345", "order BK-1" and "BKXYZ", because each contains BK somewhere. That is fine for searching and wrong for checking a whole value.

Anchors fix this. ^ means the start of the text and $ means the end. Wrapping a pattern in them, like /^[A-Z]{2}-\d{4}$/, says the entire text must be exactly this shape with nothing before or after. Most validation patterns start with ^ and end with $. If you see one that does not, ask whether that was intended. Trim the input first with trim() so a stray space from a paste does not cause a confusing rejection.

What do the i and g flags do?

Flags go after the closing slash and change how the whole pattern behaves. i makes it ignore case, so /rye/i matches rye, Rye and RYE. g makes match and replace work on every match instead of the first. You can combine them: /rye/gi.

The g flag has one trap. A pattern with g remembers where its last match ended in a property called lastIndex, and test carries on from there the next time you call it. Reusing one global pattern with test across several strings can then give false for text that clearly matches. The Break It exercise has exactly this bug. The rule of thumb: use g with match, replace and matchAll, and leave it off when you call test.

How do matchAll and named groups help?

Round brackets create a group, which captures part of a match so you can read it separately. A named group gives that part a label with (?<name>...). After a match, the captured text sits in result.groups.name. This reads much better than counting brackets to work out which number is which.

text.matchAll(pattern) gives every match with its groups, ready for a for...of loop. It requires the g flag, and throws a TypeError if it is missing. In the example below, each order line splits neatly into a quantity and a bread name, and the code reads like a description of the data.

const note = "2 rye, 1 bap, 3 sourdough";
const pattern = /(?<qty>\d+) (?<bread>[a-z]+)/g;
for (const m of note.matchAll(pattern)) {
  console.log(m.groups.bread, Number(m.groups.qty));
}

How do you validate a postcode or order code plainly?

Write the rule in words first, then translate it piece by piece. "Two capital letters, a dash, four digits" becomes [A-Z], {2}, -, \d, {4}, wrapped in ^ and $. Keep the pattern in a named constant such as orderCode so the code that uses it reads well, and add a short comment with a valid example.

Real-world formats are messier than they look. UK postcodes, for instance, come in several shapes, may be typed with or without the space, and in lower case. A practical pattern for a form is a loose check that catches typos, such as /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i, followed by the real check on the server or through an address lookup. A perfect regex for addresses, emails or names does not exist. Aim to help the person spot mistakes, never to lock out a genuine customer. Show a friendly message saying what shape you expect.

When should you not use regex?

Plain string methods are easier to read for simple jobs. text.includes("rye"), text.startsWith("BK-"), text.endsWith(".png") and text.split(",") all say what they do in words. Use them whenever they are enough. Reach for a regex when the text must follow a shape: repeated parts, optional parts, or a mix of letters and digits in a fixed order.

Avoid regex for structured formats that have proper tools. Parse URLs with new URL(), JSON with JSON.parse, dates with the date tools, and HTML with the DOM. A pattern that tries to read HTML or nested brackets will break on the first unusual input. And if you get a long pattern from an AI tool, test it against good and bad examples in the Console before trusting it, the way the demo checks are written.

Field notes

  • A regular expression describes the shape of text you want to match.
  • test answers whether the text matches the pattern.
  • Classes like [A-Z] and quantifiers like {4} describe what and how many.
  • Anchors ^ and $ make the whole text match, which validation needs.

Easy to get wrong

Without anchors the pattern matches anywhere inside the text, so a code with extra characters before or after still passes.

An unescaped dot matches any character, so £12x50 would count as a price. Escape it as \. when you mean a real full stop.

match returns null when nothing matches, and reading [0] from null throws a TypeError.

Questions people ask

What does new RegExp do?

It builds a pattern from a string, which is useful when the pattern depends on input, such as a search box. Backslashes need doubling inside the string, and any text from a person must have special characters escaped first, or a typed bracket will break the pattern.

Is there a regex for checking email addresses?

Only a loose one is worth using, such as [email protected]. The real test is sending an email. In a form, <input type="email"> already does a sensible check for you.

How can I test a pattern quickly?

Use the Console: write a pattern and call test on a few good and bad strings. Keep a short list of examples next to the pattern in a comment so the next person can rerun them.

What is the difference between replace and replaceAll?

replace with a string swaps the first match only. replaceAll with a string swaps every match. With a pattern, both need the g flag to swap every match, and replaceAll throws an error if g is missing.

Keep going