We maintain a corpus of 456 role-specific resume examples in TypeScript. Someone asked me what a good bullet point actually looks like, and rather than answer from taste I decided to measure the thing I already had. Fifteen minutes later we had a script, a set of numbers, and a conclusion. The conclusion was wrong, because the script had silently read about twelve percent of the data. This is a post about that failure mode, and then about the numbers I got once the script worked. The corpus Thirty-one TypeScript files, each exporting an array of role objects. One role looks roughly like this: { slug: 'cloud-architect', title: 'Cloud Architect Resume', category: 'Information Technology', sampleData: { summary: '...', experiences: [ { company: 'Amazon Web Services', position: 'Senior Cloud Architect', description: '- Designed multi-region architecture...\n- Led migration of...', }, ], skills: [...], }, tips: [...], } The interesting field is description. It holds a newline-delimited list of bullets as a single string, so the whole corpus of bullets is sitting there in source, greppable, without a database or an export step. Version one const descs = [...text.matchAll(/description: '((?:[^'\\]|\\.)*)'/g)].map(m => m[1]); Nothing exotic. Match description:, then a single-quoted string, allowing escapes so an apostrophe inside the text does not terminate the match early. It found 199 description strings. I did not question that, because I had no prior for what the number should be. 199 sounded like a lot of text. We computed medians off it, looked at the opener distribution, and started writing. The number that saved me was on a different line of the same output: roles 456. The slug count was fine. So 456 roles between them had 199 job descriptions, which would mean the overwhelming majority of roles had no work history at all. I knew that was false, because I had rendered these pages. Why it read twelve percent Prettier. That is the whole answer. Some of these files were formatted at a width that keeps the value on the same line: description: '- Led enterprise sales team of 30+...', Others were formatted with the value pushed onto the next line: description: '- Led enterprise sales team of 30+...', The pattern had a literal space after the colon. Every record in the second shape failed to match. Not with an error. Not with a warning. The regex simply found fewer things, and a regex that finds fewer things looks exactly like a regex running over a smaller corpus. // before /description: '...'/g // after /description:\s*('...'|"...")/g Adding \s* took the count from 199 to 1,723. The general shape of this bug is worth naming, because it is not really about regexes. A parser that silently skips malformed input converts a crash into a wrong answer. A crash costs you ten minutes. A wrong answer costs you however long you spend believing it, which in my case included a first draft of this post. Two habits that catch it: Have an expected order of magnitude before you look at the output. 456 roles times three jobs each is well over a thousand. 199 should have been alarming on sight. Cross-check one extracted count against a different one from the same run. Slugs and descriptions come from the same files through different patterns. When one is plausible and the other is not, the pattern is the suspect, not the data. What the corpus actually looks like With the parser fixed: 1,723 description blocks, of which 1,267 use the bullet format, yielding 3,815 individual bullets. Length. Median 14 words. The 10th percentile is 11, the 90th is 17, the full range is 7 to 25. That is a tighter distribution than I expected, and it is not the product of a style rule, because there was no style rule. It is what a claim plus its evidence happens to cost in English. Under about ten words you have stated a duty. Over about twenty you have written a paragraph that gets skimmed. Numbers. 89.0 percent of bullets contain at least one digit. 1,081 contain a percentage, 555 contain a currency amount. That leaves 418 bullets with no number in them at all, and reading a sample of those is instructive: they are the ones describing scope rather than outcome, which is a legitimate thing to describe when you cannot quantify it. The failure is not "has no number", it is "has neither a number nor a specific". Verbs. 332 distinct opening words across 3,815 bullets. The top ten (managed, developed, led, built, created, designed, implemented, conducted, reduced, manage) account for 40.9 percent of all openers. 143 verbs appear exactly once. That distribution is the interesting part. It is not flat and it should not be flat. A long tail of one-off verbs (architected, migrated, authored) carries the specificity, while a small head of common verbs carries the ordinary work. A resume where every bullet opens with an unusual verb reads like a thesaurus exercise. Weak openers. 149 bullets, under five percent, begin with supported, assisted or similar. Zero begin with responsible for. That one is a genuine consensus in the corpus rather than something I enforced. Density. The median role shows three bullets per position, never more than four. The 456 summaries run a median of 37 words, with the 10th to 90th percentile band sitting between 29 and 45. Duplicates: zero. Not one bullet text appears twice across 3,815. I checked because I assumed generated-looking content would have collisions, and finding none told me the corpus was written per role rather than stamped from a shared list. The part that transfers The measurement I would suggest to anyone writing bullets for their own history, since it takes a minute and needs no tooling: Count words. If a bullet is under ten or over twenty, it is probably a duty or a paragraph. Look for the digit. If there is none, check there is at least a proper noun, a system name, or a scale. Read only the first word of every bullet, top to bottom. If the same verb appears three times in one job, two of those bullets are describing the same thing. Number three is the one that finds real problems fastest, and it is the same trick as the script: look at one narrow projection of the data instead of reading all of it. The corpus these numbers came from is public. It is the set of resume examples on the builder I run, organised by role, free to open and free to preview without an account. It was not written in order to be measured, which is exactly why the measurements were worth taking. And the script stayed in the repo, with the \s* and a hard assertion that the description count is within an order of magnitude of the role count. A checker that can quietly read twelve percent of its input will do it again.