logo hsb.horse
← Back to snippets index

Snippets

Memory Size of Go Data Types

A comprehensive list of memory sizes for each Go data type and practical tips for memory-efficient coding.

Published: Updated:

Understanding the memory footprint of Go’s data types is crucial for writing memory-efficient programs. This becomes especially important when dealing with large datasets, embedded systems, or high-performance applications where data structure choices directly impact performance.

Memory Size by Data Type

Data TypeSize (Bytes)Notes
int / uint8 / 88 bytes on 64-bit systems, 4 bytes on 32-bit
int8 / uint81 / 1
int16 / uint162 / 2
int32 / uint324 / 4
int64 / uint648 / 8
float324IEEE 754 single-precision float
float648IEEE 754 double-precision float
complex648float32 × 2 (real and imaginary parts)
complex12816float64 × 2 (real and imaginary parts)
byte (alias for uint8)1
rune (alias for int32)4Unicode code point
bool1Internally uses 1 byte
string16Pointer(8) + length(8), actual data stored separately
Slice (e.g., []int)24Pointer(8) + length(8) + capacity(8)
Map (e.g., map[string]int)8Pointer to internal structure
Channel (e.g., chan int)8Pointer to internal structure
Struct (e.g., struct{})00 when no fields
Interface16Type info(8) + pointer to value(8)

※ Sizes are standard values for 64-bit systems. On 32-bit systems, pointer sizes are 4 bytes.

Sample Implementations

Checking Sizes

You can use unsafe.Sizeof to check the size of data types at runtime.

package main
import (
"fmt"
"unsafe"
)
func main() {
// Basic types
var i int
var i8 int8
var f32 float32
var f64 float64
var b bool
var r rune
fmt.Printf("int: %d bytes\n", unsafe.Sizeof(i))
fmt.Printf("int8: %d bytes\n", unsafe.Sizeof(i8))
fmt.Printf("float32: %d bytes\n", unsafe.Sizeof(f32))
fmt.Printf("float64: %d bytes\n", unsafe.Sizeof(f64))
fmt.Printf("bool: %d bytes\n", unsafe.Sizeof(b))
fmt.Printf("rune: %d bytes\n", unsafe.Sizeof(r))
// String, slice, map
var s string
var slice []int
var m map[string]int
fmt.Printf("string: %d bytes\n", unsafe.Sizeof(s))
fmt.Printf("slice: %d bytes\n", unsafe.Sizeof(slice))
fmt.Printf("map: %d bytes\n", unsafe.Sizeof(m))
// Interface
var iface interface{}
fmt.Printf("interface{}: %d bytes\n", unsafe.Sizeof(iface))
}

Checking Struct Padding

Struct memory efficiency varies based on field ordering. Padding can cause unexpected memory consumption.

package main
import (
"fmt"
"unsafe"
)
// Inefficient layout
type BadLayout struct {
a bool // 1 byte + 7 bytes padding
b int64 // 8 bytes
c bool // 1 byte + 7 bytes padding
d int64 // 8 bytes
}
// Efficient layout
type GoodLayout struct {
b int64 // 8 bytes
d int64 // 8 bytes
a bool // 1 byte
c bool // 1 byte + 6 bytes padding
}
func main() {
fmt.Printf("BadLayout: %d bytes\n", unsafe.Sizeof(BadLayout{})) // 32 bytes
fmt.Printf("GoodLayout: %d bytes\n", unsafe.Sizeof(GoodLayout{})) // 24 bytes
}

Slice Capacity and Memory Allocation

Managing slice capacity properly avoids unnecessary reallocations.

package main
import (
"fmt"
)
func main() {
// Without capacity specified, reallocations may occur with each append
s1 := []int{}
for i := 0; i < 1000; i++ {
s1 = append(s1, i)
}
// Pre-allocating capacity avoids reallocations
s2 := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
s2 = append(s2, i)
}
fmt.Printf("s1 len: %d, cap: %d\n", len(s1), cap(s1))
fmt.Printf("s2 len: %d, cap: %d\n", len(s2), cap(s2))
}

Pre-allocating Maps

Maps also benefit from initial capacity specification, reducing dynamic expansion costs.

package main
import (
"fmt"
)
func main() {
// Without capacity specified
m1 := make(map[int]string)
for i := 0; i < 1000; i++ {
m1[i] = fmt.Sprintf("value%d", i)
}
// With pre-allocated capacity
m2 := make(map[int]string, 1000)
for i := 0; i < 1000; i++ {
m2[i] = fmt.Sprintf("value%d", i)
}
fmt.Printf("m1 length: %d\n", len(m1))
fmt.Printf("m2 length: %d\n", len(m2))
}

Application Ideas and Optimization Techniques

1. Optimize Struct Field Ordering

Place larger fields first and smaller fields last to minimize padding.

// Before optimization: 32 bytes
type Before struct {
flag1 bool // 1 + 7 padding
num1 int64 // 8
flag2 bool // 1 + 7 padding
num2 int64 // 8
}
// After optimization: 24 bytes
type After struct {
num1 int64 // 8
num2 int64 // 8
flag1 bool // 1
flag2 bool // 1 + 6 padding
}

2. Set Appropriate Slice Capacity

When the final element count is predictable, specify capacity with make to avoid memory reallocations.

// When adding 1000 elements
items := make([]Item, 0, 1000)

3. Choose Between Arrays and Slices

When element count is fixed and won’t change, arrays are more memory-efficient than slices.

// Slice: 24 bytes (header) + actual data
s := []int{1, 2, 3}
// Array: 24 bytes (int × 3, data only)
a := [3]int{1, 2, 3}

4. Minimize Empty Interface Usage

interface{} consumes 16 bytes for type information and pointer. Use generics or concrete types when type is known.

// Using empty interface (16 bytes)
var v interface{} = 42
// Using concrete type (8 bytes)
var v int = 42

5. Use Builder for String Concatenation

Strings are immutable, so the + operator allocates new memory each time. Use strings.Builder for bulk concatenation.

import "strings"
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString("item")
}
result := b.String()

6. Reuse Objects with sync.Pool

Frequently created and destroyed objects can be reused with sync.Pool to reduce garbage collection pressure.

import "sync"
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
// Usage
buf := bufferPool.Get().([]byte)
defer bufferPool.Put(buf)

7. Use Pointers to Avoid Copying Large Structs

When passing large structs to functions, use pointers instead of value passing to reduce copy costs.

// Value passing: entire struct is copied
func ProcessValue(data BigStruct) {
// ...
}
// Pointer passing: only pointer (8 bytes) is copied
func ProcessPointer(data *BigStruct) {
// ...
}

8. Identify Problem Areas with Memory Profiling

Use pprof to visualize memory usage and identify areas for optimization.

import _ "net/http/pprof"
import "net/http"
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// Application logic
}

Access http://localhost:6060/debug/pprof/heap in a browser or analyze profiles with go tool pprof.

Summary

  • Basic types have explicit sizes; strings, slices, maps, and interfaces are pointer-based
  • Struct field ordering affects padding and memory efficiency
  • Specifying initial capacity for slices and maps reduces reallocation costs
  • Pass large structs by pointer and reuse frequently created objects with sync.Pool
  • Use pprof for memory profiling to identify optimization opportunities

By applying this knowledge, you can write memory-efficient Go programs.