The Go syntax and patterns you actually use daily, organized so you stop context-switching to pkg.go.dev. Covers core constructs through concurrency primitives with idiomatic Go 1.22+ examples and tips that save you a debugging session.
| Name | Syntax | Description |
|---|---|---|
| Short Variable Declaration | x := 42 | Declares and initializes inside a function. The compiler infers the type from the right-hand side. |
| Multiple Return Values | func f() (int, error) { return 0, nil } | Functions can return multiple values. Used everywhere for result + error pairs. |
| Struct Literal | p := Point{X: 1, Y: 2} | Initialize structs with named fields. Unset fields get their zero value. |
| Slice Creation | s := make([]int, 0, 10) | Allocate a slice with length 0 and capacity 10. Avoids repeated allocations during append. |
| Map Initialization | m := map[string]int{"a": 1} | Create and populate a map in one expression. Keys must be comparable types. |
| Goroutine | go func() { /* ... */ }() | Launch a function on a new goroutine. Lightweight — thousands are normal. |
| Channel Send/Receive | ch <- v / v := <-ch | Send a value into a channel or receive from it. Unbuffered channels block until both sides are ready. |
| Defer | defer f.Close() | Schedule a call to run when the enclosing function returns. Executes in LIFO order. |
| Error Check | if err != nil { return err } | The standard Go error-handling pattern. Check immediately after every fallible call. |
| Type Assertion | v, ok := i.(string) | Extract the concrete type from an interface. The comma-ok form avoids panics. |
| Range Loop | for i, v := range slice { } | Iterate over slices, maps, strings, or channels. Use _ to discard index or value. |
| Interface Definition | type Reader interface { Read([]byte) (int, error) } | Interfaces are satisfied implicitly — no implements keyword. Keep them small. |
| Pointer Receiver | func (s *Server) Start() error { } | Method that can mutate the receiver. Use pointer receivers for large structs or mutation. |
| Select Statement | select { case v := <-ch: ... default: ... } | Multiplex across channel operations. default makes it non-blocking. |
| Blank Identifier | _, err := io.Copy(dst, src) | Discard a value you don't need. Common when only the error matters. |
var x int / x := 42 / const Pi = 3.14Go has two declaration styles: var for package-level or when the type differs from the initializer, and := for concise local declarations. Constants are computed at compile time and can use iota for sequential values.
Tips
int, float64, string, bool, byte, runeGo's type system is explicit with no implicit conversions between numeric types. Every type has a well-defined zero value, which makes uninitialized variables safe to use in most contexts.
Tips
T(value)Go requires explicit type conversions for all numeric types. String conversions go through the strconv package for numbers, or direct conversion for byte/rune slices. Named types based on the same underlying type also require explicit conversion.
Tips
func name(params) (returns) { }Go functions support multiple return values, named returns, variadic parameters, and closures. Functions are first-class values that can be passed as arguments, returned, and stored in variables.
Tips
if / switch / forGo has only three control flow statements: if, switch, and for. The for keyword covers traditional loops, while loops, infinite loops, and range iteration. Switch cases don't fall through by default.
Tips
defer fn() / panic(v) / recover()defer schedules a function call to run when the surrounding function returns — essential for cleanup. panic halts normal execution and unwinds the stack. recover, called inside a deferred function, regains control after a panic.
Tips
type Name struct { } / func (s *Name) Method() { }Structs are Go's primary data-structuring tool. Methods are defined on types with receiver arguments. Embedding provides composition — promoted fields and methods behave as if they belong to the outer struct.
Tips
type Name interface { Method() }Go interfaces are satisfied implicitly — any type that implements the required methods satisfies the interface. This enables polymorphism without coupling. The empty interface (any) accepts all types.
Tips
v, ok := i.(Type) / switch v := i.(type) { }Type assertions extract the concrete value from an interface. The comma-ok form prevents panics on failed assertions. Type switches cleanly handle multiple possible concrete types.
Tips
func Name[T constraint](params) { }Generics (Go 1.18+) enable type-parameterized functions and types. Use constraints from the cmp package for ordered types, any for unconstrained types, or define custom constraint interfaces.
Tips
make([]T, len, cap) / append(s, v...) / s[low:high]Slices are Go's primary sequence type — a dynamic view over an underlying array. The slices package (Go 1.21+) provides generic helpers for sorting, searching, cloning, and manipulating slices without writing boilerplate.
Tips
make(map[K]V) / m[key] = val / delete(m, key)Maps are built-in hash tables with O(1) average access. Keys must be comparable types. The maps package (Go 1.21+) adds generic utilities for cloning, iterating keys/values, and equality checks.
Tips
strings.Contains / strings.Split / strings.BuilderGo strings are immutable UTF-8 byte sequences. The strings package provides all common operations. Use strings.Builder for efficient concatenation and range for Unicode-safe iteration.
Tips
go func() { }() / sync.WaitGroupGoroutines are lightweight concurrent functions managed by the Go runtime. sync.WaitGroup coordinates completion of a group of goroutines. Use a buffered channel as a semaphore to limit concurrency.
Tips
ch := make(chan T) / select { case ... }Channels are typed conduits for goroutine communication. Unbuffered channels synchronize sender and receiver. Buffered channels decouple them up to the buffer size. Select multiplexes across multiple channel operations.
Tips
sync.Mutex / sync.RWMutex / sync/atomicsync.Mutex provides exclusive locking for shared state. sync.RWMutex allows concurrent reads with exclusive writes. For simple counters and flags, sync/atomic operations avoid the overhead of mutex locking.
Tips
context.WithCancel / context.WithTimeoutThe context package provides cancellation signals, deadlines, and request-scoped values that propagate across API boundaries and goroutines. Every long-running or I/O operation should accept a context.Context as its first parameter.
Tips
errors.New / fmt.Errorf("%w", err)Go errors are values that implement the error interface. Use fmt.Errorf with %w to wrap errors while preserving the chain. errors.Join (Go 1.20+) combines multiple errors into one.
Tips
errors.Is(err, target) / errors.As(err, &target)errors.Is checks if any error in the wrapped chain matches a sentinel value. errors.As extracts a specific error type from the chain. Both traverse the full wrap chain, including errors combined with errors.Join.
Tips
type MyError struct { } / func (e *MyError) Error() string { }Custom error types carry structured data beyond a message string. Implement Unwrap() to participate in the error chain so errors.Is and errors.As can traverse through your custom type to the underlying cause.
Tips
Table-driven tests are the standard Go testing pattern. Each test case is a struct in a slice, iterated with t.Run for named subtests. This gives clear names in test output, easy-to-add cases, and parallel-safe subtests when combined with t.Parallel().
Functional options provide a clean, extensible API for configuring structs with many optional parameters. Each option is a function that mutates the struct. The constructor applies sensible defaults first, then applies each option in order. This avoids config structs, builder chains, and long parameter lists.
Graceful shutdown catches OS signals (SIGINT, SIGTERM), stops accepting new connections, and waits for in-flight requests to complete within a timeout. This prevents dropped requests during deployments and is essential for production HTTP servers.
errgroup (from golang.org/x/sync) manages a group of goroutines with built-in error propagation, context cancellation on first error, and concurrency limiting via SetLimit. It replaces manual WaitGroup + error channel patterns and is the standard approach for concurrent operations that can fail.
Go interfaces enable clean dependency injection without frameworks. Define small interfaces in the consumer package, accept them in constructors, and swap implementations for testing. This pattern is the foundation of testable Go architecture and avoids the need for DI containers or code generation.
Nil slice vs empty slice behave differently in JSON marshaling
A nil slice (var s []int) marshals to null, while an empty slice (s := []int{}) marshals to []. Initialize with a literal or make when you need a JSON array. Use slices.Clip or make([]T, 0) if the API contract requires [].
Goroutine leaks from abandoned channels or missing context cancellation
Every goroutine must have a guaranteed exit path. Use context.WithCancel or context.WithTimeout and select on ctx.Done(). Ensure channels are closed or the goroutine checks a done signal. Use goleak in tests to detect leaked goroutines.
Shadowed variables with := in inner scopes hide outer declarations silently
Short declaration (:=) in an inner block creates a new variable even if one with the same name exists in an outer scope. The outer variable is unchanged. Use go vet -shadow or the shadow analyzer to catch this. Assign explicitly (=) when you intend to modify the outer variable.
Appending to a sub-slice can modify the original slice's underlying array
A sub-slice (s[1:3]) shares the backing array with the original. Appending to it overwrites elements beyond the sub-slice's length if capacity allows. Use the three-index slice s[1:3:3] to restrict capacity and force a new allocation on append.
Maps are not safe for concurrent reads and writes
Concurrent map access without synchronization causes a runtime panic (not just a data race). Use sync.RWMutex to protect the map, or use sync.Map for simple concurrent patterns. Run tests with -race to detect unsafe access.
Returning a nil concrete type wrapped in an interface produces a non-nil interface
An interface value is nil only when both its type and value are nil. Returning (*MyError)(nil) as an error creates a non-nil interface with a nil pointer. Always return nil directly instead of a typed nil pointer: return nil, not return (*MyError)(nil).
Go beyond the cheatsheet with hands-on lessons and challenges.