# Why Strings and Runes Matter
Text bugs in production often come from assuming one character equals one byte. In Go, strings are UTF-8 byte sequences, so correctness requires understanding bytes, runes, and Unicode-safe iteration.
This matters for usernames, search, validation, and analytics where non-ASCII input is common.
Real-World Analogyclick to expand
Bytes are like encoded shipping boxes; runes are the actual items inside. Counting boxes is easy, but counting items requires decoding.
# Strings Are Byte Sequences
len(s) returns bytes. Indexing returns a byte. Use range to decode Unicode code points.
s := "Hello, 世界"
fmt.Println(len(s)) // bytes
fmt.Println(s[0]) // byte
for i, r := range s {
fmt.Printf("byte[%d]: %c
", i, r)
}# Runes and Unicode Safety
A rune is an int32 Unicode code point. When input can contain emoji or CJK, operate on rune slices for semantic character operations (like reverse/palindrome).
r := '🎉'
fmt.Printf("%c %d
", r, r)
s := "こんにちは"
fmt.Println(len(s)) // bytes
fmt.Println(len([]rune(s))) // characters# Efficient String Construction
For incremental building, prefer strings.Builder. For existing slices of strings, strings.Join is concise and efficient.
var b strings.Builder
for _, w := range []string{"go", "is", "fast"} {
b.WriteString(w)
b.WriteByte(' ')
}
fmt.Println(b.String())# Practice Challenge
Implement IsPalindrome(s string) bool using runes, not bytes, so it works for multi-byte Unicode characters.
package main
import "fmt"
func IsPalindrome(s string) bool {
// TODO: work correctly with multi-byte runes
}
func main() {
fmt.Println(IsPalindrome("racecar"))
fmt.Println(IsPalindrome("level"))
fmt.Println(IsPalindrome("hello"))
}⚡ Key Takeaways
- Strings are immutable byte sequences in UTF-8
- Use
rangeor rune slices for character-safe logic len(s)counts bytes, not user-visible characters- Use
strings.Builderfor efficient incremental concatenation