Data Types Basics
10 examples to get you started with Go's type system and memory behavior - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Go's type system and memory behavior - 7 basic and 3 intermediate.
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 ..
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, nil are the common zero values for numeric, string, bool, and reference-like typesnil slices and maps)var when the zero value is meaningful; use := with a literal when it is notRelated: Go's Memory Model - why value semantics start at zero values
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]int and [4]int are different types - arrays are rarely function parameters in APIslen is length; cap is capacity from the slice's first element to the end of the backing arrayRelated: Slice Internals - pointer, len, cap layout
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"])
}make or a literal before insertsync.MapRelated: Map Internals - growth and iteration order
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))
}reflect at runtimeRelated: Struct Alignment - padding and
unsafe.Sizeof
& 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)
}new(T) returns *T zero-initialized - equivalent to var v T; return &v when escapedRelated: unsafe Pointers - when
unsafe.Pointeris justified
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())
}implements keyword%T prints dynamic type - essential when debugging any parametersvar i any) differs from an interface holding a typed nil pointerRelated: Interface Internals - itables and dynamic type
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 rendezvousmake(chan T, n) buffers n sends without a matching receiverok == falseRelated: Channel Internals - blocking rules in detail
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")
}x, ok := v.(int) avoids panic on failed assertionsRelated: Nil Interface vs Nil Pointer - assertion pitfalls with typed nil
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)
}copy returns the number of elements copied - min of len(dst) and len(src)dst can affect src if they overlapappend to a new slice with sufficient capacity when you need an independent copyRelated: Slice Internals - when append reallocates
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)
}make returns an initialized (non-nil) value ready for usenew(T) is rarely needed - var v T; &v or literals are clearer for structsmake([]T, 0, n) avoids repeated append growth in hot loopsRelated: 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).
Reviewed by Chris St. John·Last updated Jul 16, 2026