OnlineTextLab
Esc

How Word Counters Count: Why Tools Disagree on the Same Text

Updated 7 min read How we check our guides

Word counters disagree because no standard says what a word is. Each tool picks a rule, usually “text between spaces” or “runs of letters and digits”, and those rules treat hyphens, numbers, URLs and Chinese text differently. A gap of a few percent between tools is normal and doesn’t mean one is broken. When a number has consequences, such as a submission limit, use the counter the receiving party will use.

One sentence, several answers

We counted this sentence with several methods:

The well-known state-of-the-art rock–roll don't cost $3.14 or 1,000 U.S. dollars & a/b test at [email protected].
MethodRuleCount
wc -w (macOS)Split on whitespace16
Python text.split()Split on whitespace16
Our Word CounterNeeds a letter or digit. Inner hyphens, apostrophes and dots stay whole16
A letter-and-digit regex[\p{L}\p{N}]+ with inner ' and -22
Intl.Segmenter in JavaScriptUnicode word boundaries (UAX #29)22

Matching totals can hide different rules. Our counter reaches 16 by a different route from wc: it doesn’t count the lone &, but it does split a/b. The two 22s also differ, which you can see by looking at the tokens. The regex splits 3.14 into 3 and 14, and it keeps well-known whole. Intl.Segmenter does the reverse, keeping 3.14 and 1,000 whole and splitting well-known:

regex:      well-known | state-of-the-art | 3 | 14 | 1 | 000 | U | S | foo | example | com
segmenter:  well | known | state | of | the | art | 3.14 | 1,000 | U.S | foo | example.com

Neither is wrong. They answer slightly different questions.

Where the rules diverge

Hyphens. state-of-the-art is 1 word for a whitespace counter and 4 for Intl.Segmenter. Style guides and teachers differ too. LibreOffice’s documentation says that words with always visible hyphens, such as plug-in, count as one.

Dashes. A dash with no spaces around it joins two words visually. Under whitespace rules, rock–roll is one word. LibreOffice treats em and en dashes as word boundaries by default, and lets you change that in the Additional separators setting. So a writer who types dashes without spaces will see different counts between Writer and a basic counter, and heavy dash use can move a long manuscript by a noticeable amount.

Numbers and symbols. Is $3.14 a word? Whitespace counters say yes. Is a standalone &? Whitespace counters say yes, while most letter-based counters say no, as it holds no letter or digit. LibreOffice’s documentation counts abc123, 1.23, "$" and http://www.example.com as four words, so it counts symbols on their own.

URLs and email addresses. A whitespace counter sees https://example.com/a-b as one word. A counter that splits on punctuation might see four.

Apostrophes. don't should be one word, but a simple pattern such as \w+ splits it into don and t. The curly form don’t, which word processors insert automatically, trips up patterns that only allow a straight ', so the same sentence counts differently depending on how it was typed.

Contractions and abbreviations. U.S. is one token by whitespace and may be split by others.

Markup. Markdown symbols, HTML tags and code all add tokens if counted raw. A standalone # or - counts as a word under whitespace rules. Strip formatting first if it matters.

What Microsoft Word does

Microsoft doesn’t publish the rules behind Word’s count. Its help page explains where to find the count and that it covers selected text if there’s a selection, but not how a word gets defined, and a reply on Microsoft’s own Q&A forum says the algorithm isn’t documented. So be careful of anyone stating precise rules with confidence, us included.

Here’s what can be said from reported behaviour on that forum:

  • Text separated by spaces is the basic unit.
  • One user tested a four-word sentence with unspaced em dashes around a middle phrase, and the spaced version of the same sentence. Both counted 4, so an unspaced em dash in mid-sentence does appear to separate words in Word, at least in that test.
  • The same user found a quirk at the edge. Two words ending in an em dash counted 2, but wrapping them in quotation marks counted 3, so a closing quotation mark after a final dash added a word.

We couldn’t run Word for this article, so treat those as reports. The practical point stands: check in Word if Word’s count is what’s being judged.

Word also has a dialog option for including text boxes, footnotes and endnotes, and that setting alone can move the count by a lot in an academic document. Our DOCX Word Counter reads a Word file in your browser, using our own rules, so it won’t always match the number Word displays.

Chinese, Japanese, Korean and Thai

Spaces don’t separate words in Chinese, Japanese or Thai, so a whitespace counter sees a whole sentence as one word. We ran these:

TextWhitespaceIntl.SegmenterOur Word Counter
日本語のテキストです1410
我爱北京天安门147
한국어 문장입니다222

Intl.Segmenter uses dictionaries, and it split the Japanese into 日本語, の, テキスト and です. It’s a real attempt at finding words, though its output can vary between browsers and versions, since segmentation is genuinely ambiguous.

Our counter takes a different approach and counts each Chinese or Japanese character as one, which is how length is normally measured in those languages. Requirements in China and Japan are usually stated in characters, not words, for this reason. Korean uses spaces between units, so it’s counted like English here.

If you’re translating or being paid by volume, agree on the unit first. “1,000 words” is meaningless for Japanese unless someone has defined how it’s measured.

Characters are not simple either

Character counts disagree for a different reason. JavaScript’s .length gives UTF-16 units, so our test emoji family 👨‍👩‍👧‍👦 has length 11, seven code points, and a single visible character. An accented é built from two code points has length 2. We covered how platforms handle this in the character limits cheat sheet.

Spaces are another variable. Our Character Counter gives counts both with and without them, and you need to know which one a form requires. Line breaks can count as 1 or 2 depending on the system, because Windows uses two characters for each.

How our counter works

Our Word Counter applies one definition across the site’s text tools, so the word counter, character counter and word frequency tool agree with each other. A word has to hold at least one letter or digit, so a lone & or dash isn’t counted. Inner apostrophes, hyphens and dots keep a token whole, which means don't, well-known, 3.14 and an email address each count as one. Spaces, slashes and em dashes separate words, so a/b is two, though an unspaced en dash doesn’t, so rock–roll is one. Each Chinese or Japanese character counts as one, and its character count treats 👨‍👩‍👧‍👦 as one character, as you see it.

Those are defensible choices rather than the only correct ones. In particular, a document with many slashes or unspaced em dashes will come out higher than a whitespace count. Everything runs in your browser and nothing is uploaded, so it’s safe to paste drafts. For a final count that someone will check, use the tool they’ll use.

Which number to trust

Essay or journal with a limit. Use whatever the institution names, which is usually Word. Check whether references, footnotes and abstracts are included. Their definition matters more than any tool’s.

A publisher or client paying per word. Agree on the tool at the start. A gap of 1 or 2 percent on a long job is real money, and a dash-heavy text can add more. Ask about numbers and hyphens if the text is technical.

SEO or blog content. It doesn’t matter. Google has no ideal word count, and a difference of 20 words between tools changes nothing. Pick one tool and stay consistent when you compare drafts.

A form with a character limit. Count characters using the form itself. Paste and see. Its count is the one that decides.

Your own code. Decide what you want before you pick an approach:

// Split on whitespace. Simple and predictable, and usually matches word processors for prose.
const a = text.trim().split(/\s+/).filter(Boolean).length;

// Unicode-aware, works for many languages, though hyphens split words.
const seg = new Intl.Segmenter("en", { granularity: "word" });
const b = [...seg.segment(text)].filter((s) => s.isWordLike).length;

We ran both on the sentence above and got 16 and 22. Document which you used.