Go has a rich set of built-in data types that are carefully designed for performance and memory efficiency. Understanding these types and their memory footprints is crucial for writing efficient Go programs. This comprehensive guide covers all Go data types with their exact memory sizes and practical examples.
Table of contents
Open Table of contents
Data Types Overview
Go’s data types are organized into several categories, each with specific characteristics and memory requirements.
Integer Types
Data Type | Size (32-bit) | Size (64-bit) | Range/Description |
---|---|---|---|
int8 | 1 byte | 1 byte | -128 to 127 |
uint8 | 1 byte | 1 byte | 0 to 255 |
byte | 1 byte | 1 byte | Alias for uint8 |
int16 | 2 bytes | 2 bytes | -32,768 to 32,767 |
uint16 | 2 bytes | 2 bytes | 0 to 65,535 |
int32 | 4 bytes | 4 bytes | -2,147,483,648 to 2,147,483,647 |
rune | 4 bytes | 4 bytes | Alias for int32 , Unicode code point |
uint32 | 4 bytes | 4 bytes | 0 to 4,294,967,295 |
int64 | 8 bytes | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
uint64 | 8 bytes | 8 bytes | 0 to 18,446,744,073,709,551,615 |
int | 4 bytes | 8 bytes | Platform-dependent signed integer |
uint | 4 bytes | 8 bytes | Platform-dependent unsigned integer |
uintptr | 4 bytes | 8 bytes | Integer large enough to store pointer bits |
Floating Point Types
Data Type | Size (32-bit) | Size (64-bit) | Range/Description |
---|---|---|---|
float32 | 4 bytes | 4 bytes | 32-bit IEEE 754 floating point |
float64 | 8 bytes | 8 bytes | 64-bit IEEE 754 floating point |
Complex Types
Data Type | Size (32-bit) | Size (64-bit) | Range/Description |
---|---|---|---|
complex64 | 8 bytes | 8 bytes | Complex number with float32 real and imaginary parts |
complex128 | 16 bytes | 16 bytes | Complex number with float64 real and imaginary parts |
Boolean Type
Data Type | Size (32-bit) | Size (64-bit) | Range/Description |
---|---|---|---|
bool | 1 byte | 1 byte | true or false |
String and Composite Types
Data Type | Size (32-bit) | Size (64-bit) | Range/Description |
---|---|---|---|
string | 8 bytes | 16 bytes | Header: pointer + length (actual string data stored separately) |
[]T (slice) | 12 bytes | 24 bytes | Header: pointer + length + capacity |
[N]T (array) | N × sizeof(T) | N × sizeof(T) | Contiguous memory for N elements |
map[K]V | 4 bytes | 8 bytes | Pointer to hash table structure |
chan T | 4 bytes | 8 bytes | Pointer to channel structure |
interface{} | 8 bytes | 16 bytes | Header: type info + value pointer |
*T (pointer) | 4 bytes | 8 bytes | Memory address |
func(...) ... | 4 bytes | 8 bytes | Function pointer |
Notes:
- Sizes for
int
,uint
, anduintptr
depend on the target architecture - String, slice, map, channel, and interface sizes are for their headers only
- Actual data is stored separately and referenced by pointers
- Array size depends on element count and element type size
Practical Examples
Let’s explore each data type category with practical examples and use cases.
Integer Types
package main
import (
"fmt"
"math"
"unsafe"
)
func integerExamples() {
// Fixed-size integers
var age int8 = 25 // Perfect for small values like age
var temperature int16 = -40 // Temperature in Celsius
var population int32 = 1_000_000 // City population
var bigNumber int64 = 9_223_372_036_854_775_807 // Maximum int64
// Unsigned integers
var pixel byte = 255 // RGB color value (0-255)
var port uint16 = 8080 // Network port number
var fileSize uint32 = 2_000_000_000 // File size in bytes
var memoryAddress uint64 = 0xFFFFFFFFFFFFFFFF
// Platform-dependent integers
var count int = 1000 // Most common integer type
var arrayIndex uint = 42 // Array/slice indexing
// Unicode rune (int32 alias)
var emoji rune = '🚀' // Unicode code point
fmt.Printf("int8 size: %d bytes, value: %d\n", unsafe.Sizeof(age), age)
fmt.Printf("int16 size: %d bytes, value: %d\n", unsafe.Sizeof(temperature), temperature)
fmt.Printf("int32 size: %d bytes, value: %d\n", unsafe.Sizeof(population), population)
fmt.Printf("int64 size: %d bytes, value: %d\n", unsafe.Sizeof(bigNumber), bigNumber)
fmt.Printf("byte size: %d bytes, value: %d\n", unsafe.Sizeof(pixel), pixel)
fmt.Printf("rune size: %d bytes, value: %c (%d)\n", unsafe.Sizeof(emoji), emoji, emoji)
}
Floating Point and Complex Types
func floatingPointExamples() {
// Single precision (32-bit)
var price float32 = 19.99
var ratio float32 = 0.618 // Golden ratio approximation
// Double precision (64-bit) - default for floating point literals
var pi float64 = 3.141592653589793
var scientificNotation float64 = 1.23e10 // 12,300,000,000
// Complex numbers
var signal complex64 = 3 + 4i // 32-bit real and imaginary parts
var wave complex128 = 1 + 2i // 64-bit real and imaginary parts
// Complex number operations
magnitude := real(wave)*real(wave) + imag(wave)*imag(wave)
fmt.Printf("float32 size: %d bytes, precision: ~7 digits\n", unsafe.Sizeof(price))
fmt.Printf("float64 size: %d bytes, precision: ~15 digits\n", unsafe.Sizeof(pi))
fmt.Printf("complex64 size: %d bytes\n", unsafe.Sizeof(signal))
fmt.Printf("complex128 size: %d bytes\n", unsafe.Sizeof(wave))
fmt.Printf("Complex wave: %v, magnitude²: %f\n", wave, magnitude)
}
Boolean and String Types
func booleanStringExamples() {
// Boolean type
var isActive bool = true
var isComplete bool // Zero value is false
var hasPermission = false // Type inferred
// String type
var name string = "Go Programming"
var empty string // Zero value is ""
var multiline = `This is a
multi-line
raw string literal`
// String operations
greeting := "Hello, " + name + "!"
length := len(name)
firstChar := name[0] // byte value
// String iteration
fmt.Println("Character by character:")
for i, char := range name {
fmt.Printf("Index %d: %c (Unicode: %d)\n", i, char, char)
}
fmt.Printf("bool size: %d byte\n", unsafe.Sizeof(isActive))
fmt.Printf("string header size: %d bytes\n", unsafe.Sizeof(name))
fmt.Printf("String length: %d, content: %q\n", length, greeting)
}
Composite Types: Arrays and Slices
func arraySliceExamples() {
// Fixed-size arrays
var scores [5]int = [5]int{95, 87, 92, 78, 85}
var matrix [3][3]int // 2D array, zero-initialized
autoSized := [...]string{"red", "green", "blue"} // Compiler counts elements
// Dynamic slices (more common)
var numbers []int // nil slice
numbers = append(numbers, 1, 2, 3, 4, 5)
// Slice with make
buffer := make([]byte, 1024) // Length 1024, capacity 1024
dynamic := make([]int, 0, 10) // Length 0, capacity 10
// Slicing operations
subset := numbers[1:4] // [2, 3, 4]
prefix := numbers[:3] // [1, 2, 3]
suffix := numbers[2:] // [3, 4, 5]
// Memory analysis
arraySize := unsafe.Sizeof(scores)
sliceHeaderSize := unsafe.Sizeof(numbers)
fmt.Printf("Array [5]int size: %d bytes\n", arraySize)
fmt.Printf("Slice header size: %d bytes\n", sliceHeaderSize)
fmt.Printf("Slice length: %d, capacity: %d\n", len(numbers), cap(numbers))
fmt.Printf("Array contents: %v\n", scores)
fmt.Printf("Slice contents: %v\n", numbers)
}
Maps and Channels
func mapChannelExamples() {
// Maps (hash tables)
var ages map[string]int // nil map
ages = make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
// Map literal
colors := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}
// Channels for communication
var messages chan string // nil channel
messages = make(chan string, 3) // Buffered channel
unbuffered := make(chan int) // Unbuffered channel
// Channel operations (in goroutines)
go func() {
messages <- "Hello"
messages <- "World"
close(messages)
}()
// Receive from channel
for msg := range messages {
fmt.Println("Received:", msg)
}
fmt.Printf("Map header size: %d bytes\n", unsafe.Sizeof(ages))
fmt.Printf("Channel header size: %d bytes\n", unsafe.Sizeof(messages))
fmt.Printf("Map length: %d\n", len(ages))
}
Pointers and Functions
func pointerFunctionExamples() {
// Pointers
var x int = 42
var ptr *int = &x // Pointer to int
var nilPtr *int // nil pointer
// Dereferencing
value := *ptr // Get value at address
*ptr = 100 // Modify value through pointer
// Function types
var operation func(int, int) int
operation = func(a, b int) int {
return a + b
}
// Function as first-class value
calculator := map[string]func(int, int) int{
"add": func(a, b int) int { return a + b },
"multiply": func(a, b int) int { return a * b },
}
result := calculator["add"](5, 3)
fmt.Printf("Pointer size: %d bytes\n", unsafe.Sizeof(ptr))
fmt.Printf("Function size: %d bytes\n", unsafe.Sizeof(operation))
fmt.Printf("Original x: %d, Modified through pointer: %d\n", 42, x)
fmt.Printf("Calculator result: %d\n", result)
}
Interfaces
// Define interfaces
type Shape interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func interfaceExamples() {
var shape Shape = Rectangle{Width: 5, Height: 3}
var empty interface{} // Can hold any value
// Interface can hold any type that implements its methods
empty = 42
empty = "hello"
empty = []int{1, 2, 3}
empty = shape
// Type assertion
if rect, ok := shape.(Rectangle); ok {
fmt.Printf("Rectangle: %+v\n", rect)
}
// Type switch
switch v := empty.(type) {
case int:
fmt.Printf("Integer: %d\n", v)
case string:
fmt.Printf("String: %s\n", v)
case Rectangle:
fmt.Printf("Rectangle area: %.2f\n", v.Area())
default:
fmt.Printf("Unknown type: %T\n", v)
}
fmt.Printf("Interface size: %d bytes\n", unsafe.Sizeof(shape))
fmt.Printf("Empty interface size: %d bytes\n", unsafe.Sizeof(empty))
}
Memory Efficiency Tips
Choosing the Right Integer Type
// Good: Use appropriate size for your data
func goodIntegerUsage() {
var age int8 = 25 // Age rarely exceeds 127
var port uint16 = 8080 // Port numbers fit in 16 bits
var fileSize int64 = 1_000_000_000 // Large files need 64 bits
}
// Less efficient: Using oversized types
func inefficientIntegerUsage() {
var age int64 = 25 // Wastes 7 bytes per age value
var port int64 = 8080 // Wastes 6 bytes per port
}
String vs Byte Slice Performance
func stringVsByteSlice() {
text := "Hello, World!"
// Strings are immutable - copying required for modifications
modified := text + " Go is awesome!"
// Byte slices are mutable - more efficient for modifications
data := []byte(text)
data = append(data, " Go is awesome!"...)
result := string(data)
fmt.Printf("Original: %s\n", text)
fmt.Printf("Modified string: %s\n", modified)
fmt.Printf("From byte slice: %s\n", result)
}
Struct Field Ordering for Memory Efficiency
// Less efficient: poor field ordering
type BadStruct struct {
Flag1 bool // 1 byte + 7 bytes padding
BigNum int64 // 8 bytes
Flag2 bool // 1 byte + 7 bytes padding
SmallNum int32 // 4 bytes + 4 bytes padding
}
// More efficient: group small fields together
type GoodStruct struct {
BigNum int64 // 8 bytes
SmallNum int32 // 4 bytes
Flag1 bool // 1 byte
Flag2 bool // 1 byte + 2 bytes padding
}
func structAlignment() {
bad := BadStruct{}
good := GoodStruct{}
fmt.Printf("BadStruct size: %d bytes\n", unsafe.Sizeof(bad)) // 32 bytes
fmt.Printf("GoodStruct size: %d bytes\n", unsafe.Sizeof(good)) // 16 bytes
}
Zero Values and Initialization
Every type in Go has a zero value - the value a variable has when declared but not explicitly initialized:
func zeroValues() {
var (
// Numeric types: 0
intVal int
floatVal float64
complexVal complex128
// Boolean: false
boolVal bool
// String: empty string
stringVal string
// Pointers, slices, maps, channels, functions: nil
ptrVal *int
sliceVal []int
mapVal map[string]int
chanVal chan int
funcVal func()
// Arrays: zero value of element type
arrayVal [3]int
// Structs: zero value of each field
structVal struct {
Name string
Age int
}
)
fmt.Printf("int zero value: %d\n", intVal)
fmt.Printf("float64 zero value: %f\n", floatVal)
fmt.Printf("bool zero value: %t\n", boolVal)
fmt.Printf("string zero value: %q\n", stringVal)
fmt.Printf("slice zero value (nil): %v\n", sliceVal == nil)
fmt.Printf("array zero value: %v\n", arrayVal)
fmt.Printf("struct zero value: %+v\n", structVal)
}
For more Go programming fundamentals, check out Learn Go in 60 Seconds.