Ship internal tools in hours, not weeks. Real auth, users, jobs, audit logs, and cohesive UI included. Early access $249 $499 → [Get it now]

How to count characters in a string in JavaScript

Counting characters in strings is essential for validation, text analysis, character limits, and implementing features like text counters or input constraints in JavaScript applications. With over 25 years of experience in software development and as the creator of CoreUI, I’ve implemented character counting in components like textarea inputs, tweet composers, form validation, and text analytics where knowing exact character counts enhances user experience and data processing. From my expertise, the most direct and efficient solution is using the built-in length property, which provides instant access to the character count. This approach is simple, performant, and works consistently across all JavaScript environments without any additional processing.

Use the length property to count the number of characters in a string.

const text = 'Hello World'
const characterCount = text.length
// Result: 11

The length property returns the total number of characters in the string, including letters, numbers, spaces, and special characters. In this example, 'Hello World'.length returns 11 because there are 11 characters total (including the space between words). The count includes all visible and invisible characters like spaces, tabs, and line breaks. For most text, length is all you need — but be aware that some emoji and special Unicode characters may count as multiple units due to JavaScript’s UTF-16 encoding.

1. Counting Visible Characters (Excluding Whitespace)

When implementing character limits, you often want to count only visible characters. Removing whitespace before counting gives a more accurate representation of actual content length.

const text = '  Hello   World  '

// Total length (includes all spaces)
console.log(text.length)
// Result: 17

// Visible characters only (no spaces at all)
console.log(text.replace(/\s/g, '').length)
// Result: 10

// Trimmed length (remove leading/trailing whitespace only)
console.log(text.trim().length)
// Result: 13

// Count only alphanumeric characters
const mixed = 'Hello, World! 123'
console.log(mixed.replace(/[^a-zA-Z0-9]/g, '').length)
// Result: 13

// Count only letters
console.log(mixed.replace(/[^a-zA-Z]/g, '').length)
// Result: 10

// Count only digits
console.log(mixed.replace(/[^0-9]/g, '').length)
// Result: 3

Choose the counting method that matches your validation requirements. For CoreUI form controls with maxLength, the standard length including spaces is typically what you want. For analytics or content density calculations, counting only visible characters is more useful. For trimming whitespace before counting, see how to trim whitespace from a string in JavaScript.

2. Counting Unicode Characters and Emoji Correctly

JavaScript’s length property counts UTF-16 code units, not visual characters. Emoji and characters outside the Basic Multilingual Plane use two code units (a surrogate pair), so length overcounts them.

// Simple ASCII — length works correctly
console.log('hello'.length)
// Result: 5

// Accented characters — still correct
console.log('café'.length)
// Result: 4

// Emoji — length overcounts!
console.log('😀'.length)
// Result: 2 (one emoji = two UTF-16 code units)

console.log('👨‍👩‍👧‍👦'.length)
// Result: 11 (family emoji is composed of multiple code points + joiners)

// Use spread operator or Array.from for true character count
console.log([...'😀'].length)
// Result: 1

console.log([...'Hello 😀'].length)
// Result: 7

console.log(Array.from('café').length)
// Result: 4

// Intl.Segmenter for grapheme-accurate counting (handles composed emoji)
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' })
const segments = [...segmenter.segment('👨‍👩‍👧‍👦')]
console.log(segments.length)
// Result: 1 (correctly counts as one visual character)

// Practical function for visual character count
const visualLength = (text) => {
  if (typeof Intl.Segmenter !== 'undefined') {
    return [...new Intl.Segmenter('en', { granularity: 'grapheme' }).segment(text)].length
  }
  return [...text].length // fallback
}

console.log(visualLength('Hello 👨‍👩‍👧‍👦!'))
// Result: 8

Use [...str].length for most cases where emoji might appear. Use Intl.Segmenter when you need pixel-accurate counting of composed emoji (family, skin tones, flags). Intl.Segmenter is supported in all modern browsers and Node.js 16+.

3. Counting Words in a String

Word counting is a common requirement for editors, content limits, and SEO tools. The naive split(' ') approach breaks with multiple spaces — use a regex-based split instead.

// Correct word counting
const countWords = (text) => {
  const trimmed = text.trim()
  if (!trimmed) return 0
  return trimmed.split(/\s+/).length
}

console.log(countWords('Hello World'))
// Result: 2

console.log(countWords('  Multiple   spaces   here  '))
// Result: 3

console.log(countWords(''))
// Result: 0

console.log(countWords('   '))
// Result: 0

console.log(countWords('OneWord'))
// Result: 1

// Comprehensive text statistics
const textStats = (text) => {
  const chars = text.length
  const charsNoSpaces = text.replace(/\s/g, '').length
  const words = countWords(text)
  const lines = text.split('\n').length
  const sentences = text.split(/[.!?]+/).filter(s => s.trim()).length

  return { chars, charsNoSpaces, words, lines, sentences }
}

const article = 'Hello World. This is a test.\nSecond line here!'
console.log(textStats(article))
// { chars: 47, charsNoSpaces: 38, words: 9, lines: 2, sentences: 3 }

The split(/\s+/) pattern splits on one or more whitespace characters (spaces, tabs, newlines), correctly handling multiple consecutive spaces. Always trim() first to avoid counting empty strings at the boundaries. For checking if the string has content at all, see how to check if a string is empty in JavaScript.

4. Counting Occurrences of a Specific Character

Counting how many times a particular character or substring appears is useful for validation, parsing, and data analysis.

// Count a single character
const countChar = (text, char) => {
  let count = 0
  for (const c of text) {
    if (c === char) count++
  }
  return count
}

console.log(countChar('hello world', 'l'))
// Result: 3

console.log(countChar('banana', 'a'))
// Result: 3

// Using split (concise alternative)
const countCharSplit = (text, char) => text.split(char).length - 1

console.log(countCharSplit('mississippi', 's'))
// Result: 4

// Using regex for case-insensitive counting
const countCharRegex = (text, char) => {
  const matches = text.match(new RegExp(char, 'gi'))
  return matches ? matches.length : 0
}

console.log(countCharRegex('Hello World', 'h'))
// Result: 1 (case-insensitive)

// Count all character frequencies
const charFrequency = (text) => {
  const freq = {}
  for (const char of text.toLowerCase()) {
    if (char !== ' ') {
      freq[char] = (freq[char] || 0) + 1
    }
  }
  return freq
}

console.log(charFrequency('hello'))
// { h: 1, e: 1, l: 2, o: 1 }

// Find the most frequent character
const mostFrequent = (text) => {
  const freq = charFrequency(text)
  return Object.entries(freq).sort((a, b) => b[1] - a[1])[0]
}

console.log(mostFrequent('mississippi'))
// Result: ['i', 4]

The split(char).length - 1 trick works because splitting a string by a character produces one more segment than there are occurrences. For substring counting, use the regex approach with match(). For related string search operations, see how to check if a string contains a substring in JavaScript.

5. Building a Real-Time Character Counter

A character counter is one of the most common UI patterns — showing users how many characters they’ve typed and how many remain.

// Character counter logic
const createCounter = (maxLength) => {
  return (text) => {
    const current = text.length
    const remaining = maxLength - current
    const percentage = Math.min((current / maxLength) * 100, 100)

    return {
      current,
      remaining: Math.max(remaining, 0),
      percentage: Math.round(percentage),
      isOver: current > maxLength,
      display: `${current}/${maxLength}`
    }
  }
}

const tweetCounter = createCounter(280)

console.log(tweetCounter('Hello World'))
// { current: 11, remaining: 269, percentage: 4,
//   isOver: false, display: '11/280' }

console.log(tweetCounter('x'.repeat(300)))
// { current: 300, remaining: 0, percentage: 107,
//   isOver: true, display: '300/280' }

// Counter with word limit
const createWordCounter = (maxWords) => {
  return (text) => {
    const trimmed = text.trim()
    const words = trimmed ? trimmed.split(/\s+/).length : 0
    const remaining = maxWords - words

    return {
      words,
      remaining: Math.max(remaining, 0),
      isOver: words > maxWords,
      display: `${words}/${maxWords} words`
    }
  }
}

const bioCounter = createWordCounter(50)
console.log(bioCounter('Hello world this is a test'))
// { words: 6, remaining: 44, isOver: false, display: '6/50 words' }

This pattern pairs well with CoreUI form controls — bind the counter to the input’s onChange event and display the remaining count below the field. Use the isOver flag to toggle CoreUI form validation styles.

6. Counting Bytes (String Size in Memory)

Sometimes you need the byte size of a string rather than its character count — for example, when enforcing database column limits or API payload sizes.

// Count bytes using TextEncoder (UTF-8)
const byteLength = (text) => new TextEncoder().encode(text).length

console.log(byteLength('hello'))
// Result: 5 (ASCII: 1 byte per character)

console.log(byteLength('café'))
// Result: 5 (é is 2 bytes in UTF-8)

console.log(byteLength('你好'))
// Result: 6 (CJK characters: 3 bytes each)

console.log(byteLength('😀'))
// Result: 4 (emoji: 4 bytes in UTF-8)

// Compare character count vs byte count
const samples = ['hello', 'café', '你好世界', '😀🎉']
samples.forEach(text => {
  console.log(
    `"${text}": ${text.length} chars, ${[...text].length} graphemes, ${byteLength(text)} bytes`
  )
})
// "hello": 5 chars, 5 graphemes, 5 bytes
// "café": 4 chars, 4 graphemes, 5 bytes
// "你好世界": 4 chars, 4 graphemes, 12 bytes
// "😀🎉": 4 chars, 2 graphemes, 8 bytes

// Truncate to byte limit (useful for database constraints)
const truncateToBytes = (text, maxBytes) => {
  const encoder = new TextEncoder()
  const encoded = encoder.encode(text)
  if (encoded.length <= maxBytes) return text

  const decoder = new TextDecoder()
  return decoder.decode(encoded.slice(0, maxBytes))
}

console.log(truncateToBytes('Hello 你好世界!', 10))
// Result: 'Hello 你' (truncated at 10 bytes)

TextEncoder encodes to UTF-8, which is the most common encoding for web APIs and databases. ASCII characters use 1 byte, European accented characters use 2 bytes, CJK characters use 3 bytes, and emoji use 4 bytes. Always use byte counting when working with storage limits or network payloads.

Best Practice Note:

For most UI character counters, text.length is sufficient — it matches the behavior of HTML maxlength attributes and CoreUI form controls. Use [...text].length when emoji support matters. Use Intl.Segmenter for pixel-accurate counting of composed emoji. Use TextEncoder for byte-level counting against database or API limits. For extracting numbers from counted text, see how to extract numbers from a string in JavaScript.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team