The number nobody checks
"7 min read" appears above a great deal of writing on the internet, and a surprising amount of it is wrong in the same direction: too high, because the estimate counted the code.
Counting code at prose speed is the single most common mistake in this small genre of function. A tutorial with three long snippets is not a twenty-minute read. Nobody reads a code block; they scan it, decide whether it is the thing they came for, and move on.
A version that keeps its word
The fix is to strip what is not prose before you count:
const WORDS_PER_MINUTE = 220
export function countWords(text: string): number {
const prose = text
.replace(/```[\s\S]*?```/g, " ") // fenced code
.replace(/`[^`]*`/g, " ") // inline code
.replace(/!\[[^\]]*\]\([^)]*\)/g, " ") // images
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") // link text, not the URL
return prose.trim().match(/\S+/g)?.length ?? 0
}
export const readingMinutes = (words: number) =>
Math.max(1, Math.round(words / WORDS_PER_MINUTE))Two hundred and twenty words per minute is the conventional silent-reading rate for adults on familiar material. It is a convention, not a measurement, and the honest thing is to call the output an estimate wherever you print it.
Why it matters
A reading-time estimate is a small promise about how much of someone's evening you are asking for. If it is routinely wrong by a factor of three, readers stop reading it, and you have spent bytes on an ornament.