Data Types Basics
10 examples to get you started with Go's type system and memory behavior - 7 basic and 3 intermediate.
Prerequisites
Install Go 1.26.x and run examples from any module directory:
go version # expect go1.26.x
mkdir -p /tmp/go-types-demo && cd /tmp/go-types-demo
go mod init example.com/types-demoSave each snippet as main.go (or combine into one main with separate functions) and run go run ..
Basic Examples
1. Zero Values
Every variable declared without an initializer gets its type's zero value.
package main
import "fmt"
func main() {
var i int
var s string
var p *int
fmt.Printf("int=%d string=%q ptr=%v\n", i, s, p)
}0,"",false,nilare the common zero values for numeric, string, bool, and reference-like types- Zero value is not "unset" - it is a valid state you must design for (especially
nilslices and maps) - Use
varwhen the zero value is meaningful; use:=with a literal when it is not
Related: Go's Memory Model - why value semantics start at zero values
2. Arrays vs Slices
Arrays have fixed size in the type; slices are dynamic views over a backing array.
package main
import "fmt"
func main() {
var arr [3]int = [3]int{1, 2, 3}
sl := []int{10, 20, 30}
fmt.Println(len(arr), len(sl), cap(sl))
}[3]intand[4]intare different types - arrays are rarely function parameters in APIs- Slices are the idiomatic sequence type - headers passed by value, backing array shared
lenis length;capis capacity from the slice's first element to the end of the backing array
Related: Slice Internals - pointer, len, cap layout
3. Maps and nil Behavior
Maps associate keys with values. A nil map is readable but not writable.
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m["missing"]) // 0, no panic
m = make(map[string]int)
m["go"] = 1
fmt.Println(m["go"])
}- Reading a missing key returns the value type's zero value - use the comma-ok form to detect presence
- Writing to a nil map panics - always
makeor a literal before insert - Maps are not safe for concurrent writes without a mutex or
sync.Map
Related: Map Internals - growth and iteration order
4. Struct Literals and Field Tags
Structs group fields with value semantics unless you store pointers.
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
u := User{ID: 1, Name: "Ada"}
b, _ := json.Marshal(u)
fmt.Println(string(b))
}- Exported fields start with uppercase - encoding/json only sees exported fields
- Struct assignment copies all fields - including slice and map fields (headers alias)
- Field tags are metadata strings parsed by
reflectat runtime
Related: Struct Alignment - padding and
unsafe.Sizeof
5. Pointers and Address Operators
& takes an address; * dereferences. Go has no pointer arithmetic in safe code.
package main
import "fmt"
func bump(n *int) {
*n++
}
func main() {
x := 10
bump(&x)
fmt.Println(x)
}- You cannot take the address of a map index or most temporaries - the compiler enforces memory safety
new(T)returns*Tzero-initialized - equivalent tovar v T; return &vwhen escaped- Nil pointer dereference panics - check before use in defensive APIs
Related: unsafe Pointers - when
unsafe.Pointeris justified
6. Empty and Typed Interfaces
interface{} (or any) holds any value; other interfaces hold values with specific methods.
package main
import "fmt"
type Greeter interface {
Greet() string
}
type Person struct{ Name string }
func (p Person) Greet() string { return "hello, " + p.Name }
func describe(v any) {
fmt.Printf("%T %v\n", v, v)
}
func main() {
var g Greeter = Person{Name: "Ada"}
describe(g)
fmt.Println(g.Greet())
}- Interfaces are satisfied implicitly - no
implementskeyword %Tprints dynamic type - essential when debugginganyparameters- A nil interface (
var i any) differs from an interface holding a typed nil pointer
Related: Interface Internals - itables and dynamic type
7. Channels for Signaling
Channels transfer values between goroutines with blocking semantics.
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 42
v := <-ch
fmt.Println(v)
}make(chan T)is unbuffered - send and receive rendezvous- Capacity
make(chan T, n)buffers n sends without a matching receiver - Closing a channel signals completion - receivers get zero value and
ok == false
Related: Channel Internals - blocking rules in detail
Intermediate Examples
8. Type Assertions and Type Switches
Extract concrete types from interfaces safely.
package main
import "fmt"
func parse(v any) {
switch x := v.(type) {
case int:
fmt.Println("int", x*2)
case string:
fmt.Println("string", len(x))
default:
fmt.Printf("other %T\n", x)
}
}
func main() {
parse(21)
parse("go")
}- Two-value assertion
x, ok := v.(int)avoids panic on failed assertions - Type switches dispatch on dynamic type - common in JSON decoders and plugin APIs
- Asserting to the wrong concrete type panics in the single-value form
Related: Nil Interface vs Nil Pointer - assertion pitfalls with typed nil
9. Slice Sharing and copy
Subslicing shares a backing array; copy duplicates elements into an existing slice.
package main
import "fmt"
func main() {
src := []int{1, 2, 3, 4}
dst := make([]int, 2)
n := copy(dst, src[1:])
fmt.Println(dst, n, src)
}copyreturns the number of elements copied - min of len(dst) and len(src)- Subslicing does not copy data - mutating
dstcan affectsrcif they overlap - Use
appendto a new slice with sufficient capacity when you need an independent copy
Related: Slice Internals - when append reallocates
10. make vs new
make initializes slices, maps, and channels; new allocates a single value and returns a pointer.
package main
import "fmt"
func main() {
s := make([]byte, 0, 64)
m := make(map[string]struct{})
c := make(chan struct{})
p := new(int)
fmt.Printf("slice cap=%d map=%v chan=%v *p=%d\n", cap(s), m != nil, c != nil, *p)
}makereturns an initialized (non-nil) value ready for usenew(T)is rarely needed -var v T; &vor literals are clearer for structs- Capacity in
make([]T, 0, n)avoids repeated append growth in hot loops
Related: Data Types & Memory Best Practices - choosing layouts for performance
Stack 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).