Skip to content

Word & Character Counter — JavaScript source

Live word, character, sentence, and paragraph counts plus reading-time estimate as you type.

This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

/**
 * word-counter - JavaScript
 * =========================
 * Polyglot showcase port of CosmoDev's "word-counter" tool, ported from
 * the canonical TypeScript at src/lib/wordCount.ts (the live web library
 * and unit-test surface).
 *
 * Display source - part of CosmoDev's polyglot tool pages
 * (dev.cosmolabs.org), where each tool's pure logic is shown in six
 * languages side by side. Pure string/data logic; no crypto.
 *
 * Public surface: countWords, countChars, countCharsNoSpaces,
 *                 countSentences, countParagraphs, readingTimeMin.
 */

/**
 * Count whitespace-separated words.
 *
 * trim() drops leading/trailing whitespace; split(/\s+/) then breaks on
 * runs of whitespace. filter(Boolean) guards the empty-string case:
 * "".split(/\s+/) === [""], which must not count as one word. Empty or
 * whitespace-only input therefore yields 0.
 *
 * @param {string} text
 * @returns {number}
 */
export function countWords(text) {
  return text.trim().split(/\s+/).filter(Boolean).length;
}

/**
 * Total character count, whitespace included.
 *
 * NOTE: JavaScript's string .length counts UTF-16 code units, so a single
 * astral symbol (most emoji) counts as 2. The sibling ports count Unicode
 * code points, which agrees for all BMP text; see their notes.
 *
 * @param {string} text
 * @returns {number}
 */
export function countChars(text) {
  return text.length;
}

/**
 * Character count with all whitespace stripped.
 *
 * @param {string} text
 * @returns {number}
 */
export function countCharsNoSpaces(text) {
  return text.replace(/\s/g, '').length;
}

/**
 * Count sentences: maximal runs of non-terminal characters followed by one
 * or more sentence terminators (. ! ?).
 *
 * Text with no terminal punctuation ("Hello world") therefore counts as 0
 * sentences - this matches the canonical behaviour.
 *
 * @param {string} text
 * @returns {number}
 */
export function countSentences(text) {
  const matches = text.match(/[^.!?]+[.!?]+/g);
  return matches === null ? 0 : matches.length;
}

/**
 * Count paragraphs separated by one or more blank lines (2+ newlines).
 * Segments that are blank after trimming do not count.
 *
 * @param {string} text
 * @returns {number}
 */
export function countParagraphs(text) {
  return text
    .split(/\n{2,}/)
    .filter((segment) => segment.trim() !== '')
    .length;
}

/**
 * Estimated reading time in whole minutes at 200 words per minute.
 *
 * 0 for empty input, otherwise at least 1. Integer arithmetic -
 * Math.floor((words + 100) / 200) - reproduces Math.round(words / 200)
 * exactly for non-negative words and keeps every language port in lock-step
 * (Python's round() is banker's rounding, which would diverge on .5).
 *
 * @param {number} words
 * @returns {number}
 */
export function readingTimeMin(words) {
  if (words <= 0) return 0;
  return Math.max(1, Math.floor((words + 100) / 200));
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →