Question

Count Characters in a Go String: Bytes, Runes, and UTF-8

gostringcharacterstring-length

Question

How can I get the number of characters in a string in Go?

For example, a function should return 5 for the string "hello". However, len(s) returns the number of bytes rather than the number of Unicode characters. For example, len("£") returns 2 because £ uses two bytes when encoded as UTF-8. How can I count it as one character?

Short Answer

You will learn why Go's len function counts UTF-8 bytes, how to count Unicode code points (runes), and why user-visible characters can sometimes require a different kind of count.

Concept

Go strings are immutable sequences of bytes. They commonly contain UTF-8 encoded text, where one visible symbol may use more than one byte.

len(s) always reports the byte length of a string:

fmt.Println(len("hello")) // 5
fmt.Println(len("£"))     // 2

To count Unicode code points, which Go calls runes, use utf8.RuneCountInString from the unicode/utf8 package:

utf8.RuneCountInString("£") // 1

This distinction matters whenever an application accepts international text. A byte count is useful for storage, network protocols, and byte-level processing. A rune count is usually better for rules such as “a name must contain at most 30 Unicode characters.”

One important detail: a rune is not always the same as one user-visible character. For example, e followed by a combining accent can look like é while consisting of two runes. Some emoji also consist of multiple runes. Counting visible characters requires grapheme-cluster segmentation, which is a separate task.

Mental Model

Think of a UTF-8 string as a row of packages:

  • Bytes are the individual pieces of packaging.
  • Runes are the items represented by those packages.
  • Visible characters are what a person sees on a screen; sometimes several items combine into one visible symbol.

For ASCII text such as "hello", each package holds one item, so bytes and runes both total 5.

For "£", UTF-8 needs two bytes to store one rune. Therefore, its byte count is 2, but its rune count is 1.

Take Quiz

Syntax and Examples

Import unicode/utf8 and call utf8.RuneCountInString when you need a Unicode code-point count.

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    text := "Hello, £世界"

    fmt.Println("Bytes:", len(text))
    fmt.Println("Runes:", utf8.RuneCountInString(text))
}

len(text) counts the bytes used by the UTF-8 encoding. utf8.RuneCountInString(text) decodes that UTF-8 text and counts each rune.

You can also convert to []rune:

count := len([]rune("£世界"))
fmt.Println(count) // 3

This works because each element in []rune represents one rune. However, use utf8.RuneCountInString when you only need the count: it communicates the intention directly and avoids creating a rune slice.

Step by Step Execution

Consider this program:

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "A£"

    fmt.Println(len(s))
    fmt.Println(utf8.RuneCountInString(s))
}

Step by step:

  1. s contains A and £.
  2. In UTF-8, A uses one byte.
  3. In UTF-8, £ uses two bytes.
  4. len(s) counts 1 + 2, so it prints 3.
  5. utf8.RuneCountInString(s) decodes the UTF-8 data into two runes: A and £.
  6. The rune count is 2.

Output:

3
2

Real World Use Cases

  • Form validation: Limit a display name to a maximum number of Unicode code points rather than bytes.
  • Text analytics: Count letters or symbols in user comments containing non-English languages.
  • Command-line tools: Process UTF-8 text files without treating multibyte characters as multiple characters.
  • APIs: Validate text fields consistently before saving data.
  • Storage limits: Use len when a database, protocol, or file format specifies a limit in bytes; use rune counting when a product rule is stated in characters.
  • Logging and debugging: Compare byte size with rune count when diagnosing text encoding issues.
Take Quiz

Real Codebase Usage

In production Go code, choose the count based on the requirement, then make that choice explicit.

Validate a rune limit

func validateUsername(name string) error {
    if name == "" {
        return fmt.Errorf("username is required")
    }

    if utf8.RuneCountInString(name) > 30 {
        return fmt.Errorf("username must contain at most 30 characters")
    }

    return nil
}

The early returns keep validation readable. Before counting, many applications also verify that external input is valid UTF-8:

if !utf8.ValidString(input) {
    return fmt.Errorf("input must be valid UTF-8")
}

Use bytes for byte-based limits

if len(payload) > 1_000_000 {
    return fmt.Errorf("payload is too large")
}

Network payloads and file sizes are normally measured in bytes, so len is the right tool here.

Avoid repeatedly converting a string to []rune inside a loop. If you need to iterate through text, use range, which decodes UTF-8 runes as it goes.

Common Mistakes

Assuming len counts text characters

s := "£"
fmt.Println(len(s)) // 2, not 1

len is correct here; it is reporting bytes. Use utf8.RuneCountInString(s) for a rune count.

Indexing a string as if indexes identify runes

s := "£"
fmt.Println(s[0]) // 194: the first UTF-8 byte, not the £ rune

String indexing returns a byte. To iterate over runes, use range:

for _, r := range "£世界" {
    fmt.Printf("%c\n", r)
}

Converting to []rune only to count

count := len([]rune(s))

This is valid, but it creates a slice. Prefer utf8.RuneCountInString(s) when counting is the only goal.

Treating rune count as the number of visible symbols

utf8.RuneCountInString("é") // 2 in a decomposed representation

The result can be two because the string may contain an e rune and a combining accent rune, even though it looks like one é. If a user interface must count visible characters exactly, use a Unicode grapheme-cluster library and define the product behavior clearly.

Ignoring invalid UTF-8 from external data

Go strings can hold arbitrary bytes. For untrusted text, check utf8.ValidString if your application requires valid UTF-8.

Comparisons

NeedRecommended approachWhat it counts
Measure encoded string sizelen(s)Bytes
Count Unicode code pointsutf8.RuneCountInString(s)Runes
Count runes after conversion or enable rune indexinglen([]rune(s))Runes
Iterate through Unicode textfor _, r := range sRunes during iteration
Count user-visible symbols such as combined emojiGrapheme-cluster segmentation libraryGrapheme clusters

len([]rune(s)) and utf8.RuneCountInString(s) usually produce the same count for valid UTF-8 text. The utf8 function is preferable when you only need the count; []rune is useful when you need the converted rune values afterward.

A range loop is different from both: it is primarily for processing each rune, not merely counting them.

Take Quiz

Cheat Sheet

import "unicode/utf8"

s := "£世界"

len(s)                     // number of bytes
utf8.RuneCountInString(s)  // number of runes: 3
len([]rune(s))             // number of runes: 3
utf8.ValidString(s)        // whether s is valid UTF-8
  • Go string indexes access bytes, not runes.
  • ASCII characters use one byte each, so byte and rune counts match for ASCII-only text.
  • UTF-8 runes can use multiple bytes.
  • Use range to iterate through runes.
  • A rune count may differ from the number of visible characters for combining marks and some emoji.
  • Use len deliberately when the requirement is measured in bytes.

FAQ

Does len count characters in Go?

No. For strings, len returns the number of bytes.

How do I count Unicode characters in a Go string?

Use utf8.RuneCountInString(s) after importing unicode/utf8.

Why does len("£") return 2 in Go?

Go strings usually use UTF-8. The £ rune is encoded using two UTF-8 bytes, and len counts those bytes.

Is len([]rune(s)) the same as utf8.RuneCountInString(s)?

For valid UTF-8 strings, both count runes. Prefer utf8.RuneCountInString when you only need a count.

Can I use s[i] to get the ith character?

Not reliably for Unicode text. s[i] returns the byte at index i. Convert to []rune if you need rune-based indexing, while remembering that this allocates a slice.

Do runes always match what users see as one character?

No. A visible character may be formed from multiple runes, such as a letter plus a combining accent or a multi-rune emoji sequence.

Should I validate UTF-8 before counting?

Do so when input must be valid text. Use utf8.ValidString(s) for data received from external sources.

Take Quiz

Mini Project

Description

Build a small command-line text inspector. It accepts a string and reports its byte count, rune count, UTF-8 validity, and every rune it contains. This makes the difference between encoded bytes and Unicode text observable.

Goal

Create a Go program that correctly inspects ASCII and Unicode strings without confusing bytes with runes.

Requirements

Use a Unicode string that contains both ASCII and non-ASCII text. Print the string's byte length using len. Print its rune count using utf8.RuneCountInString. Report whether the string is valid UTF-8. Iterate over the string with range and print each rune.

Take Quiz

Keep learning

Related questions

View more