Go · cheat sheet

Go

Goroutines and the scheduler, channels and select, slices, maps, interfaces, errors, context, sync, generics and testing: the Go facts interviewers probe.

The Go facts a backend round tends to touch, written for Go 1.23 with later additions labelled by version.

Language basics

Topic Remember
Declarations var x int (zero value), x := 1 (inside functions only), const Pi = 3.14 (untyped constant)
Zero values 0, "", false, nil for pointers, slices, maps, channels, funcs and interfaces
Visibility Capitalised names are exported from the package; everything else is package-private
Multiple results v, err := f(); _ discards a value
Loops only for: three-clause, condition-only, infinite, range
Loop variables per-iteration since Go 1.22 (module go line ≥ 1.22)
Range forms slices, arrays, strings (runes), maps, channels, integers (1.22), iterator funcs (1.23)
switch no fallthrough by default; cases can be expressions; switch { case x > 0: }
Init order package vars (dependency order), then init() functions, then main
Built-ins len, cap, make, new, append, copy, delete, close, min/max/clear (1.21)
  • new(T) returns a *T to a zeroed value. make is only for slices, maps and channels and returns the initialised value, not a pointer.
  • Strings are immutable byte sequences. len(s) counts bytes; for i, r := range s yields runes with byte offsets.
  • Unused imports and unused local variables are compile errors.

Slices

s := make([]int, 0, 10)   // len 0, cap 10
s = append(s, 1, 2, 3)    // always keep the result
t := s[1:3]               // shares s's array: len 2, cap 9
u := s[1:3:3]             // full slice expression: cap 2, next append copies
c := slices.Clone(t)      // independent copy (Go 1.21)
go
  • A slice is a header: pointer, length, capacity. Passing it copies the header, not the elements.
  • append writes in place when len < cap, otherwise allocates a bigger array. Two slices over one array can overwrite each other.
  • A function that appends must return the slice; the caller’s length does not change otherwise.
  • var s []int (nil) and []int{} (empty) both have length 0 and both work with append and range; they differ in s == nil and JSON (null vs []).
  • A small slice of a huge array keeps the whole array alive: clone it.
  • slices.Sort, slices.Contains, slices.Index, slices.Equal (1.21); slices.Sorted, slices.Collect with iterators (1.23).

Maps

  • Create with make(map[K]V) or a literal. A nil map reads as empty but panics on write.
  • v, ok := m[k] tells a missing key from a zero value. delete(m, k) is a no-op if absent.
  • Iteration order is unspecified and varies: for _, k := range slices.Sorted(maps.Keys(m)) for stable output (1.23).
  • Elements are not addressable: m[k].Field = v does not compile for struct values.
  • Keys must be comparable: no slices, maps or funcs as keys.
  • Not safe for concurrent use: concurrent writes can crash with fatal error: concurrent map writes, which recover cannot catch.
  • Swiss-table implementation since Go 1.24.

Structs, methods & interfaces

Concept Rule
Value receiver func (p Point) X() works on a copy; in the method set of Point and *Point
Pointer receiver func (c *Cache) Put() can mutate; only in the method set of *Cache
Satisfaction implicit: a type implements an interface if its method set has the methods
Compile-time check var _ io.Reader = (*MyReader)(nil)
Embedding promotes fields and methods; composition, not inheritance, no virtual dispatch
any alias for interface{} (1.18)
Type assertion w, ok := r.(io.Writer); without ok a failed assertion panics
Type switch switch v := x.(type) { case string: ... case nil: ... }
  • An interface value is a (type, value) pair and is nil only when both are nil. A nil *MyErr returned as error is not a nil error.
  • Accept interfaces, return concrete types. Define small interfaces in the package that uses them.
  • Comparing two interfaces that hold the same uncomparable dynamic type (a slice, map or func) panics at run time.

Errors, defer, panic

if err := save(u); err != nil {
	return fmt.Errorf("save user %d: %w", u.ID, err) // %w keeps the chain, %v flattens it
}

var nf *NotFoundError
switch {
case errors.Is(err, sql.ErrNoRows): // value match anywhere in the chain
case errors.As(err, &nf):           // type match, fills nf
}
go
  • Errors are values; return them last and handle each once (log or return).
  • errors.Join(e1, e2) and multiple %w verbs wrap several errors (1.20).
  • defer runs at function return in LIFO order; arguments are evaluated immediately; deferred closures can modify named results.
  • recover() works only when called directly by a deferred function in the panicking goroutine. An unrecovered panic in any goroutine kills the process (exit status 2).
  • panic(nil) raises *runtime.PanicNilError since Go 1.21, so recover returns non-nil.

Goroutines & the scheduler

  • go f(x) starts a goroutine; x is evaluated now. When main returns, the program exits without waiting for other goroutines.
  • Starting stack about 2 KB, grows by copying. Hundreds of thousands of goroutines are normal.
  • G goroutine, M OS thread, P scheduling context with a local run queue. GOMAXPROCS = number of Ps; defaults to CPU count and, since Go 1.25 on Linux, the container CPU limit.
  • Idle Ps steal work; blocking syscalls hand the P to another thread; network waits park on the netpoller (epoll/kqueue).
  • Asynchronous preemption since Go 1.14: tight loops no longer starve other goroutines.

Channels & select

Operation nil channel open channel closed channel
ch <- v blocks forever blocks until a receiver (unbuffered) or buffer space panic
<-ch blocks forever blocks until a value is available buffered values, then zero value with ok == false
close(ch) panic succeeds panic
  • The sender closes; with many senders, a coordinator closes after a WaitGroup wait.
  • select picks randomly among ready cases; default makes it non-blocking; a nil channel disables a case.
  • Directional types: chan<- T (send only), <-chan T (receive only).
  • Timeout: case <-ctx.Done(): or case <-time.After(d):. Since Go 1.23 (module go ≥ 1.23), unreferenced timers are garbage-collected even if never stopped, and timer channels are unbuffered so Reset/Stop never leave a stale value.

sync & atomics

Tool Use
sync.Mutex / RWMutex guard shared state; not reentrant; never copy after use
sync.WaitGroup Add before go, defer Done(), Wait(); wg.Go(f) since 1.25
sync.Once, OnceFunc, OnceValue lazy one-time init (the last two since 1.21)
sync.Pool reuse temporary objects to cut allocations; contents can be dropped during GC
sync.Map append-mostly caches or disjoint keys per goroutine
atomic.Int64, atomic.Bool, atomic.Pointer[T] lock-free counters and flags (typed since 1.19)
errgroup.Group (golang.org/x/sync) goroutines that return errors; WithContext, SetLimit
  • Data race = concurrent access, at least one write, no synchronisation. Find with go test -race ./... (prints WARNING: DATA RACE).
  • go vet catches copied locks, lost cancel funcs, misused Printf verbs and more.

context

ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel() // always: releases the timer and the parent link
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
go
  • First parameter, named ctx; never stored in a struct; never nil (start from context.Background()).
  • Cancellation flows parent to children only. ctx.Err() is context.Canceled or context.DeadlineExceeded after Done() closes.
  • WithCancelCause (1.20), AfterFunc, WithoutCancel, WithDeadlineCause, WithTimeoutCause (1.21).
  • WithValue only for request-scoped data (request ID, trace, principal) with an unexported key type.

Generics

func Map[T, U any](s []T, f func(T) U) []U {
	out := make([]U, 0, len(s))
	for _, v := range s {
		out = append(out, f(v))
	}
	return out
}

type Number interface{ ~int | ~int64 | ~float64 }
func Sum[T Number](xs []T) (t T) { for _, x := range xs { t += x }; return }
go
  • Constraints: any, comparable, unions with ~ (underlying type), cmp.Ordered (1.21).
  • Methods cannot have their own type parameters. Generic type aliases arrived in Go 1.24.
  • Iterators (1.23): iter.Seq[V] is func(yield func(V) bool); for v := range seq works on it.

Memory & performance

  • Escape analysis decides stack vs heap: go build -gcflags=-m. Returning &local is legal; it just escapes.
  • GC: concurrent tri-colour mark-sweep, non-generational, non-moving. GOGC=100 default; GOMEMLIMIT soft cap (1.19).
  • Preallocate (make([]T, 0, n)), reuse buffers, use strings.Builder, avoid []byte/string conversions in hot loops.
  • Profile: import _ "net/http/pprof", then go tool pprof http://host/debug/pprof/profile; heap, goroutine, block and mutex profiles; go tool trace for scheduler latency.

Tooling & testing

Command Does
go mod init example.com/app, go mod tidy create module; add missing and drop unused requirements
go get example.com/lib@v1.4.0 add or upgrade a dependency
go build ./..., go run ., go install ./cmd/app build, run, install
go test ./... -race -cover tests with race detector and coverage
go test -run 'TestParse/empty' -v one subtest
go test -bench . -benchmem benchmarks with allocation counts
go test -fuzz FuzzParse native fuzzing (1.18)
go vet ./..., gofmt -l . static checks, formatting
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build cross-compile a static binary
  • Modules use minimal version selection: the highest of the required minimums, never “latest”. go.sum pins content hashes.
  • Major versions 2+ change the import path (example.com/lib/v2).
  • Tests: TestXxx(t *testing.T) in _test.go; table-driven cases with t.Run; t.Helper(), t.Cleanup(), t.Parallel(); httptest for HTTP; b.Loop() in benchmarks (1.24); testing/synctest for time-dependent concurrent code (1.25).

Gotchas & traps

  • x := ... inside an if or for block shadows an outer x, often an outer err.
  • A goroutine blocked on a channel nobody serves leaks forever; give every goroutine an exit path.
  • for loop with defer inside keeps every resource open until the function returns.
  • time.Now() comparisons: use t.Equal(u), not ==, because of monotonic clock readings and locations.
  • JSON only sees exported fields; unexported ones are silently skipped.
  • http.Get with the default client has no timeout; set http.Client{Timeout: ...} or a context deadline.
  • Forgetting resp.Body.Close() leaks connections.
  • os.Exit and log.Fatal skip deferred calls.

Practise with the full question bank on the Go topic page.

esc