Toolium

Why Two Word Counters Give You Two Different Numbers

Hemanth Gedda7 min read

Counting words feels like one of the simplest problems in software. Split the text on whitespace and report the resulting count. That definition works for English about 95% of the time. The other 5% is what makes word counters disagree with each other and what made me rewrite Toolium's word counter twice before it produced sensible numbers for a Japanese or Chinese paragraph. This post is the explanation of what "word count" actually means and why every implementation gets a slightly different answer.

The naive definition and where it fails

The simplest word-count implementation is one line of code: text.trim().split(/\s+/).length. Take the text, trim leading and trailing whitespace, split on runs of whitespace, count the pieces. This works for normal English prose. For "The quick brown fox jumps over the lazy dog," it returns 9. Correct.

Where it falls apart:

  • Contractions. Is "don't" one word or two? Microsoft Word counts it as one. The naive split-on-whitespace also gets one (because there is no space). But "he's" might be intended as "he is" (two words) or "he has" (two words). Counting it as one is the convention but it is arguably an undercount.
  • Hyphenated compounds. Is "well-known" one word or two? Word counts it as one. Some style guides count it as two. The naive splitter gets one (because there is no space).
  • Punctuation only. A paragraph that ends in "..." followed by no space and another sentence loses a word boundary. "Hello. World" returns 2; "Hello.World" returns 1.
  • Multiple consecutive spaces. These usually do not affect the count because the regex collapses runs of whitespace, but some implementations treat each space as a separator and count empty strings.
  • Languages without spaces. Chinese, Japanese, and Thai do not put spaces between words. The naive splitter sees the entire paragraph as one word.

The CJK problem and Intl.Segmenter

The non-space-using language case was the one that prompted the Toolium tool's biggest rewrite. The first version split on whitespace and reported "1" for any Chinese paragraph, which was obviously wrong. The second version split on every character for non-Latin scripts, which overcounted by a factor of three to five because Chinese words are typically two or three characters.

The correct primitive is Intl.Segmenter, which is a relatively new browser API (Chrome 87+, Safari 14.1+) that performs locale-aware text segmentation. For Latin scripts it agrees with the whitespace-based count. For CJK scripts it consults built-in dictionaries to identify word boundaries, producing counts that match what a native speaker would count.

The Toolium Word Counter uses Intl.Segmenter when available and falls back to a regex-based heuristic when not. The tool tells you which method it used so you know whether to trust the count for non-English text.

Sentences, paragraphs, and the abbreviation trap

Counting sentences is a regex problem at heart: how many times does a sentence-ending punctuation mark (., !, ?) appear followed by whitespace and a capital letter?

The trap is abbreviations. "Dr. Smith went to Mr. Jones's house." has three periods but only one sentence. The naive regex counts three sentences. The fix is to maintain a list of common abbreviations (Dr., Mr., Mrs., St., etc., i.e., e.g., a.m., p.m.) and treat them as non-terminal.

The Toolium counter does this and gets within a few percent of Microsoft Word's count for normal English prose. Unusual abbreviations or scientific text with lots of "et al." may produce different counts.

Character counts: with and without spaces, and emojis

Character count is simpler: how many Unicode code points are in the text? The "without spaces" variant excludes whitespace characters; the "with spaces" variant includes them.

Emojis are the modern wrinkle. A simple emoji like ๐Ÿ˜€ is one Unicode code point and counts as one character. Compound emojis joined with zero-width joiners (๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ, the family) are technically multiple code points but render as a single grapheme. The "character count" depends on whether you mean code points or graphemes.

The Toolium counter reports code points (one per emoji building-block), because that is the unit most platforms enforce. Twitter's 280-character limit counts code points; SMS counts code points; meta description SEO limits count code points. If you specifically need grapheme counts, Intl.Segmenter with the "grapheme" granularity option produces them.

Reading time

The convention is roughly 200-250 words per minute for adult silent reading. Toolium uses 225 words per minute, which is the middle of the range. For technical content, the real reading time is usually higher because readers slow down for unfamiliar concepts. For light reading, lower. The estimate is a rough guide, not a precise measurement.

What word count is useful for, beyond hitting a target

  • Essay submission limits. If your assignment is 500-700 words, the counter tells you immediately whether you are in range. Better than handing in something the grader will mark down for being too long.
  • SEO blog post targets. Content that targets a specific keyword often performs better at 1500-3000 words because Google interprets length as a depth signal (within limits; padding for the sake of length is detectable and penalized).
  • Cover letters and resumes. Most hiring managers spend under 30 seconds on a first read. Cover letters in the 250-400 word range get read; longer ones do not. The counter tells you when you have gone over.
  • Social media character limits. Twitter is 280 characters, LinkedIn is 3,000, Mastodon is configurable per server. The character count tells you whether your draft fits.

Why I prefer real-time counters

The reason this tool updates the count as you type rather than requiring a "Count" button: editing-feedback loops should be tight. When you are trimming a 700-word cover letter to 400, you want to see the count drop with each cut so you know when to stop. A button-based counter forces you to break flow.

The Toolium counter runs the count on every keystroke. For very long inputs (50,000+ words) this could theoretically slow down typing, but the Intl.Segmenter pass is fast enough that even multi-megabyte inputs feel responsive. Tested on a 1 MB text file: 80ms per recount, which is unnoticeable.

Try the tool mentioned in this article

Open tool