Numeric Types, Strings, Runes & Byte Slices
Go's built-in types separate fixed-width numbers, UTF-8 text, Unicode code points (runes), and raw bytes.
Search across all documentation pages
Go's built-in types separate fixed-width numbers, UTF-8 text, Unicode code points (runes), and raw bytes.
Mixing them without conversion is a common source of off-by-one and encoding bugs.
Integers and floats have explicit sizes; untyped constants adapt to context.
Strings are immutable UTF-8 byte sequences.
A rune is an alias for int32 representing a Unicode code point.
Use []byte for binary data and []rune when you need character-indexed text operations.
Quick-reference recipe card - copy-paste ready.
package main
import (
"fmt"
"strconv"
"unicode/utf8"
)
func main() {
s := "café"
fmt.Println(utf8.RuneCountInString(s)) // rune count: 4
n, err := strconv.Atoi("42")
if err != nil {
panic(err)
}
fmt.Println(n + 1)
}When to reach for this:
utf8.RuneCountInString, not len(string).strconv, not fmt.Sscanf, for clear errors.int64(x) when widening or narrowing.string and []byte as distinct unless copying for APIs.package main
import (
"fmt"
"strconv"
"unicode/utf8"
)
func main() {
price := "19.99"
amount, err := strconv.ParseFloat(price, 64)
if err != nil {
fmt.Println("parse error:", err)
return
}
title := "Hello, 世界"
fmt.Println("bytes:", len(title))
fmt.Println("runes:", utf8.RuneCountInString(title))
for i, r := range title {
fmt.Printf("byte index %d: %U\n", i, r)
}
b := []byte(title)
b[0] = 'h' // mutates copy only if you reassign string from b
fmt.Println(string(b))
}What this demonstrates:
strconv.ParseFloat for string-to-number conversion with errors.len on strings measures UTF-8 bytes, not runes.range over strings decodes runes and yields byte indices.[]byte(string) allocates a mutable copy of bytes.int, int8..int64, uint, uint8..uint64), floats (float32, float64), and complex64/complex128.int and uint size is platform-dependent (32 or 64 bit); prefer sized types for serialization.[]byte copies unless using unsafe (avoid).U+FFFD when ranging.| Type | Typical use | Overflow |
|---|---|---|
int | indexes, lengths | wraps in unchecked arithmetic |
int64 | IDs, timestamps | same |
uint8 | raw bytes | same |
float64 | measurements | IEEE rounding errors |
big.Int (package) | arbitrary precision | not built-in |
| Operation | Code | Result |
|---|---|---|
| Byte length | len(s) | UTF-8 byte count |
| Rune count | utf8.RuneCountInString(s) | Unicode code points |
| Iterate runes | for i, r := range s | byte index + rune |
| Byte slice | []byte(s) | mutable copy |
| Rune slice | []rune(s) | one rune per code point |
// strconv is the idiomatic string/number bridge
i, err := strconv.ParseInt("ff", 16, 64)
f := strconv.FormatFloat(3.5, 'f', 2, 64)
// rune literal vs byte
var letter rune = 'A' // int32 code point
var raw byte = 'A' // uint8 ASCIIlen for character count - "é" has len 2 in UTF-8. Fix: utf8.RuneCountInString or range.int32(x) silently - Large int64 to int32 wraps. Fix: check bounds before conversion.[]byte derived from string - Safe for the slice, but reassigning string(b) creates new string; original unchanged. Fix: treat conversion as copy.fmt.Sscanf for parsing - Weak error reporting vs strconv. Fix: prefer strconv.Parse* functions.== - 0.1+0.2 issues. Fix: epsilon compare or integers in cents.s[i] is a byte, not a rune; may split multibyte sequences. Fix: range or convert to []rune for indexing.| Alternative | Use When | Don't Use When |
|---|---|---|
[]byte | IO, crypto, wire formats | You need character operations |
[]rune | Reverse, insert by character index | Memory matters for huge ASCII text |
strings.Builder | Building strings in loops | Few concatenations (+ is fine) |
math/big | Decimal money, crypto | Simple counters |
type rune = int32 - a Unicode code point.
Rune literals like '世' are typed as untyped rune constants.
Immutability enables safe sharing and efficient substring slices that reference the same backing array.
Mutations go through []byte or strings.Builder.
strconv.Itoa(42)
strconv.FormatInt(42, 10)
fmt.Sprintf("%d", 42) // allocates more; fine for logsThey are identical types; byte is an alias for uint8 used for ASCII/binary data.
Invalid bytes are replaced with rune utf8.RuneError (U+FFFD) and width 1.
No - use integer minor units (cents) or shopspring/decimal / big.Rat for exact arithmetic.
Constants without explicit type (like 42 or 3.14) take the type demanded by the context with default int/float64/complex128.
Use strings.TrimSpace and utf8.ValidString when validating user text.
Avoid trimming by raw byte counts for display widths.
for _, b := range []byte(s) yields bytes, not runes.
Use for _, r := range string(b) or utf8.DecodeRune.
Shortcut for ParseInt(s, 10, 0) returning int.
It errors on overflow for the platform int size.
Use strings.Builder in loops; the compiler optimizes simple + chains of few parts.
json.Marshal encodes both as strings or base64 depending on field type.
Pick one type in your API structs deliberately.
[]byte and []runeStack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026