Writing a Pattern That Holds Up, and Knowing What It Will Cost
Last timeWhich Match You Actually Get
A date pattern built from nothing against a fixed list of eight strings, one change per failing string, ending with the two numbers that price it: the paths through it and the length of the input.
You want to accept a date written as four digits, a dash, two digits, a dash,
two digits. Writing something that matches 2026-11-06 takes a few seconds.
Knowing that you are finished takes longer, and knowing what the thing will do
when somebody pastes forty characters of nonsense into the field is a separate
question again. Both have answers, and both come from the same two habits:
decide in advance which strings the pattern has to judge, and count the paths
through it once it is written.
Start from the strings, not from the pattern
Here is the list. Eight strings, one of them accepted and seven rejected, each
rejected for a different reason.
| step | input | wanted | first right at | what that version added | what happened |
|---|---|---|---|---|---|
| 1 | 2026-11-06 | accept | V1 | digits and literal dashes in the right order | The only string on the list that must come out accepted, and the only one an example-driven pattern would ever have been tested against. |
| 2 | 2026/11/06 | reject | V1 | nothing, the dashes were already literal | Free. A dash written as itself only matches a dash, so the first attempt already refuses this. |
| 3 | 2026-1-6 | reject | V2 | exact counts, four digits then two then two | The first real change. A repetition of one or more digits accepts a single digit, and a month written as 1 is the sort of thing a form sends. |
| 4 | 12026-11-06 | reject | V3 | a start anchor | A search finds the date inside the longer string. No accepted example ever asks for this, which is why it is the change people omit. |
| 5 | 2026-11-06 ok | reject | V3 | an end anchor | The same omission at the other end. Trailing rubbish after a valid date is the classic way a parser accepts a line it should have rejected. |
| 6 | 2026-11-06 and a newline | reject | V4 | a whole-input match instead of an anchored search | The end anchor is satisfied just before a newline that ends the input, so V3 accepts this and reports a match ten characters long against an eleven character value. |
| 7 | 2026-13-45 | reject | V5 | named ranges for the month and the day | Correct shape, impossible numbers. Two digits accepts every number from 00 to 99, and only a choice between cases narrows it. |
| 8 | 2026-02-30 | reject | V6 | a calendar check in the code after the pattern | The only one on the list that shape cannot settle, because whether 30 is a legal day depends on the month beside it. |
The order matters more than it looks. Building towards a list means the pattern
grows by changes that something forced, which gives every piece of it a reason
a reviewer can ask for and get. Building from examples you expect to accept
gives you a pattern that accepts those examples and an unbounded number of
other things, because no string you want accepted will ever ask you for an
anchor, and none will ever ask you for a bound.
The lesson stops here
9 more paragraphs to go
You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.
The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.
See the planThe contentsThis is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents