— notes

Runes, Bytes, and Graphemes in Go

2 min read#go
On this page
  1. 1. Bytes
  2. 2. Runes
  3. 3. Grapheme clusters
  4. Quick reference

I once ran into this problem while handling names in Tamil and emoji in a Go web app: a string that looked short wasn’t, and reversing it produced gibberish.

graph TD
    G["Grapheme Cluster\n(what users see)"]
    R1["Rune 1\n(code point)"]
    R2["Rune 2\n(combining mark)"]
    B1["Bytes\n(1-4 per rune)"]
    B2["Bytes\n(1-4 per rune)"]
    G --> R1
    G --> R2
    R1 --> B1
    R2 --> B2

1. Bytes

Go represents strings as immutable UTF-8 byte sequences.

s := "வணக்கம்"
fmt.Println(len(s)) // 21

21 bytes, not visible symbols. Every Tamil character can span 3 bytes.

2. Runes

string to []rune gives you code points.

rs := []rune(s)
fmt.Println(len(rs)) // 7

7 runes, but some Tamil graphemes (like “க்”) combine two runes: க + ்.

3. Grapheme clusters

Go’s standard library stops at runes. For visible character units, use github.com/rivo/uniseg.

for gr := uniseg.NewGraphemes(s); gr.Next(); {
    fmt.Printf("%q\n", gr.Str())
}

That outputs what a human reads: வ, ண, க், க, ம்.

graph LR
    T["வணக்கம் (Tamil)"]
    T --> |"len()"| BY["21 bytes"]
    T --> |"[]rune"| RU["7 runes"]
    T --> |"uniseg"| GR["5 graphemes"]

Quick reference

Task Approach
Count code points utf8.RuneCountInString(s)
Count visible units Grapheme iteration (uniseg)
Reverse text Parse into graphemes, reverse slice, join
Slice safely Only use s[i:j] on grapheme boundaries