Quick Reference · a small, fast, statically-typed language

go cheat sheet up to 1.26

Go keeps a tiny surface: one loop, no classes, no exceptions, no generics-soup. Errors are values, concurrency is built in, and everything compiles to a single self-contained binary. Learn the handful of models below and the rest is just standard library.

setup & tooling types & syntax functions & errors composites & interfaces concurrency new in 1.22–1.26 gotcha most common

Distilled & cross-checked across: go.dev/ref/spec · go.dev/doc/effective_go · go.dev/tour · go.dev release notes 1.21–1.26 · pkg.go.dev · gobyexample.com · antonz.org

Two mental models that carry most of Go
1 · BUILD — SOURCE COMPILES TO ONE STATIC BINARY .go source packages + imports gofmt-clean go build gc compiler + linker static analysis, no VM links single binary no runtime to install no deps · just copy & run runs on linux · amd64 / arm64 darwin · windows cross-compile: GOOS / GOARCH 2 · RUN — GOROUTINES SHARE BY COMMUNICATING goroutines G G G G G G go f() · cheap (KBs) thousands at a time ch ←→ channel select wait on many channels at once + ctx.Done() to cancel scheduled by Go scheduler · G–P–M P (logical procs) P P = GOMAXPROCS (container-aware) M (OS threads) M M CPU cores many G onto few threads

Two ideas carry most of Go. Left/top: source compiles fast to a single self-contained native binary — no VM, no interpreter, nothing to install on the target. Bottom: a go statement spawns a cheap goroutine; goroutines share by communicating over channels, and the runtime scheduler multiplexes thousands of them onto a handful of OS threads.

main.go — the whole language in one screen
package main

import (
    "errors"
    "fmt"
)

func div(a, b int) (int, error) {      // multi-return: value + error
    if b == 0 {
        return 0, errors.New("divide by zero")
    }
    return a / b, nil
}

func main() {
    ch := make(chan int)               // an unbuffered channel
    go func() { ch <- 42 }()           // a goroutine sends
    fmt.Println(<-ch)                  // main receives -> 42

    if q, err := div(10, 2); err != nil {
        fmt.Println("error:", err)     // the idiomatic error check
    } else {
        fmt.Println("result:", q)      // 5
    }
}

Modern Go · 1.18 → 1.26

The language stayed small but sharpened fast. The releases that matter, and the headline additions — the violet 1.N chips throughout the cards mark where each feature landed.

1.18 generics 1.21 min/max/clear · slices/maps 1.22 loop var fix · range int 1.23 range-over-func iterators 1.24 generic aliases · os.Root 1.25 synctest · container GOMAXPROCS 1.26 new(expr) · Green Tea GC

Generics 1.18

func Keys[K comparable, V any](m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m { r = append(r, k) }
    return r
}

Type parameters in [ ] brought type-safe containers and algorithms to Go — no more interface{} casts.

Iterators 1.23

func Count(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := range n { if !yield(i) { return } }
    }
}
for v := range Count(3) { use(v) }

A function of the right shape is now rangeable — custom iteration with plain for range.

Loop variable fix 1.22

for i := range 3 {
    go func() { print(i) }()   // 0 1 2, not 3 3 3
}

Each iteration gets a fresh loop variable, quietly killing Go's most infamous closure bug.

new(expr) 1.26

age := new(42)         // *int -> 42
p := Person{Age: new(y)} // optional field

new now takes an expression, not just a type — retires the ubiquitous ptr(x) helper.

Type-safe errors 1.26

if pe, ok := errors.AsType[*fs.PathError](err); ok {
    log(pe.Path)
}

errors.AsType[T] is a generic, faster, allocation-free twin of errors.As.

Leak detector + GC 1.26

// GOEXPERIMENT=goroutineleakprofile
// /debug/pprof/goroutineleak
// Green Tea GC now on by default

A profile flags goroutines blocked forever on unreachable channels; the new GC cuts collection overhead 10–40%.

01Setup & Toolchainone binary does it all
02Program Anatomyevery file starts the same
03Basic Types & Literalsa small, explicit set
04Variables & Constants:= is your friend
05Operatorsno surprises
06Strings, Runes & BytesUTF-8 all the way
07Control Flowone loop to rule them all
08Functionsfirst-class, multi-return
09Errorsvalues, not exceptions
10defer, panic & recovercleanup & escape hatch
11Arrays & Slicesthe workhorse
12Mapshash tables, built in
13Structsplain data, composed
14Methods & Receiversfunctions with a receiver
15Interfacessatisfied implicitly
16Genericstype parameters
17Goroutines & Channelsshare by communicating
18select, sync & contextcoordinate goroutines
19Packages & Moduleshow code is organised
20Testing & Benchmarksbatteries included

Four things worth picturing

The models behind Go's most common surprises — slices, interfaces, the scheduler, and channels.

a slice = {ptr, len, cap}

A slice is a small header pointing into a shared backing array. Two slices can overlap — and append within spare capacity writes straight through.

slice header ptr · len=2 · cap=4 a b _ _ x len → visible cap → append lands here (shared!)

interfaces are satisfied implicitly

No implements: a type fits an interface just by having the methods. Inside, the value is a (type, value) pair — which is why a non-nil interface can still hold a nil pointer.

type User String() string auto-fits Stringer String() string typed-nil trap iface{ type=*User, val=nil } ≠ nil

many goroutines, few threads

The scheduler multiplexes thousands of goroutines (G) over a small pool of OS threads (M), one per logical processor (P). A blocked G simply parks — the thread runs another.

goroutines P P GOMAXPROCS M M CPU 9 goroutines → 2 threads → 2 cores blocked G parks; the thread picks up the next one

unbuffered vs buffered channels

An unbuffered channel is a hand-off: the sender blocks until a receiver is ready (a rendezvous). A buffered channel is a mailbox: sends only block when it's full.

unbuffered — rendezvous snd rcv both block until they meet buffered — mailbox (cap 3) snd send blocks only when full close(ch): receivers drain, then get the zero value only the sender closes · send on a closed channel panics

Worth memorizing

err != nilcheck every error — don't discard it with _
typed nilan interface holding a (*T)(nil) is NOT == nil
appendalways s = append(s, x); it may reallocate
slice aliasinga[i:j] shares the backing array — writes leak
range copiesfor _,v := range gives a COPY; use &s[i] to mutate
:= vs =:= makes new vars — watch shadowing in inner scopes
receiversvalue receiver mutates a copy; use *T to change state
ExportedCapitalized = public; lowercase = package-private
mapsnil map reads ok, WRITES panic; order is random
closeonly the sender closes; send on closed = panic
deferargs eval immediately; bodies run LIFO at return
goroutine leakblocking forever on an unreachable channel (1.26 detects)
no ternary/whilejust for, error values, and struct embedding
stringimmutable UTF-8; len = bytes; range yields runes
gofmtone true format — run it, don't argue