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.
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:
scontainsAand£.- In UTF-8,
Auses one byte. - In UTF-8,
£uses two bytes. len(s)counts1 + 2, so it prints3.utf8.RuneCountInString(s)decodes the UTF-8 data into two runes:Aand£.- 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
lenwhen 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.
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
| Need | Recommended approach | What it counts |
|---|---|---|
| Measure encoded string size | len(s) | Bytes |
| Count Unicode code points | utf8.RuneCountInString(s) | Runes |
| Count runes after conversion or enable rune indexing | len([]rune(s)) | Runes |
| Iterate through Unicode text | for _, r := range s | Runes during iteration |
| Count user-visible symbols such as combined emoji | Grapheme-cluster segmentation library | Grapheme 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.
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
rangeto iterate through runes. - A rune count may differ from the number of visible characters for combining marks and some emoji.
- Use
lendeliberately 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.
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.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.