Engineering Journal
Schema Editor
Schema Editor

letters-then-digits matched 167 things and there were only 36

2026-08-21

TLDR: ^[A-Z]{1,3}\d{1,4}[A-Z]?$ is a perfect description of what a component designator looks like. It is also a perfect description of A0, ADC12 and PA7, and a microcontroller schematic has four times more pin names than parts.

The error

No exception. Just a component list with 167 entries on a sheet that has 36 parts, most of them named things like ADC13 and PC2.

The bug class

A pattern that captures the form of an identifier in a domain where the domain also defines a set of valid values.

You reach for the pattern because the form is easy to describe and the set feels like a hardcoded table you will have to maintain. In domains with a controlled vocabulary, the table is the correct answer and the pattern is a guess that happens to work on your test data.

Currency codes, airport codes, HTTP methods, chemical element symbols, ISO country codes, unit prefixes. In every case a shape-based pattern will match a large number of strings that are not members.

The concrete case

Component designators on a schematic: R1, C12, IC7A, RN3A, JP6. One to three letters, a number, an optional sub-part letter.

const DESIGNATOR_RE = /^[A-Z]{1,3}\d{1,4}[A-Z]?$/;

Correct for every real designator. Also correct for:

A0  A1  A2 ... A7          Arduino analog pin labels
ADC0 ... ADC15             ATmega ADC channel names
PA0 ... PA7, PB0 ... PB7   port pin names
PWM3, TXD2, RXD1           peripheral function names

A microcontroller sheet labels every pin. There are far more pins than parts, so the pattern was wrong about four times out of five.

And it did not fail quietly. Each false designator then competed for a nearby symbol in the spatial join, and a false match near a real part can win on distance and take a name that belongs to something else.

The fix

Designator prefixes are standardised. IEC 81346 and IEEE 315 define the set, and CAD tools follow it closely because the whole point of a designator is that a person reading a different drawing recognises it.

const DESIGNATOR_PREFIXES = [
    'LED', 'ZD', 'RN', 'FB', 'JP', 'SW', 'TP', 'BT', 'VR', 'IC', 'DS',
    'B', 'C', 'D', 'F', 'G', 'J', 'K', 'L', 'M', 'P', 'Q', 'R', 'S', 'T',
    'U', 'V', 'X', 'Y', 'Z',
];
const DESIGNATOR_RE = new RegExp(
    '^(' + DESIGNATOR_PREFIXES.join('|') + ')(\\d{1,4})([A-Z])?$');

167 matches became 36, which is exactly the number of parts on the sheet.

Note the ordering. Multi-character prefixes come first, because regex alternation is first-match-wins and R|RN matches RN3A as R followed by a number that starts with N, which then fails the digit class and rejects a valid designator. Sorting longest-first is not a style preference, it is what makes the alternation correct.

The same bug, one field over

Part numbers had it too:

const PART_RE = /^(?=.[A-Za-z])(?=.\d)[A-Za-z0-9][A-Za-z0-9_./+-]{4,}$/;

Letters, digits, at least five characters. That matched XTAL1 and ADC15, which are pin names, and produced IC3 [XTAL1]: a microcontroller whose part number is one of its own pins.

Real part numbers are either long or carry a separator. PMV48XP is seven characters. CD1206-S01575 has a hyphen. XTAL1 is five characters of letters-then-digits and neither.

const PART_RE = /^(?=.[A-Za-z])(?=.\d)(?:[A-Za-z0-9][A-Za-z0-9_./+-]{6,}|[A-Za-z0-9][A-Za-z0-9]*[_./-][A-Za-z0-9_./-]+)$/;

Preventing the class

Ask whether the domain publishes a list. If it does, the list is the pattern. Maintaining thirty prefixes is less work than debugging why a country parser accepted XX.

Write the negative cases before the positive ones. Ours are now a unit check: A0, ADC12, PA7, PB0, XTAL1 and ADC15 must not classify as designator or part. Those six assertions would have caught both bugs on the first run.

Count the matches against a number you know. We knew roughly how many parts the sheet had. 167 was not a subtle discrepancy, and the count was available long before anyone read the output.

Read this post in the full Engineering Journal →