DEV Community

lappy
lappy

Posted on • Originally published at zenn.dev

Go 1.26 Cheat Sheet

This cheat sheet is based on Go 1.26.

Table of Contents


Program structure

package main  // Every executable must be in package main.

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println("Hello, Go 1.26")
    os.Exit(0)
}
Enter fullscreen mode Exit fullscreen mode
  • Every Go source file starts with a package declaration.
  • An executable program uses package main and a main function.
  • Group standard-library and third-party imports separately.

Variables, constants, and types

Variable declarations and inference

// var can infer the type from an initializer.
var name = "Gopher" // string
var age int          // No initializer: specify a type. Zero value: 0.
var id int64 = 42    // Use an explicit type when int is not what you want.

// Short declaration: inside functions only.
city := "Tokyo"

// Declare multiple variables in a block.
var (
    x, y int = 1, 2
    msg  string
)
Enter fullscreen mode Exit fullscreen mode

Use var name = "Gopher" when an initializer makes the type clear. Without an initializer, write the type explicitly. A := declaration is only valid inside a function, and at least one name on its left side must be new.

Types and zero values

Category Types Zero value
Numbers integers, floats, complex numbers 0 (0 + 0i for complex)
Strings string ""
Booleans bool false
Arrays [N]T every element is T's zero value
Structs struct every field has its zero value
Reference-containing types pointers, slices, maps, channels, functions, interfaces nil
Defined types e.g. type UserID int the underlying type's zero value
Category Types Notes
Boolean bool true or false
String string immutable sequence of bytes (usually UTF-8, but invalid UTF-8 is allowed)
Signed integers int, int8, int16, int32, int64 int is platform-sized (32 or 64 bit)
Unsigned integers uint, uint8, uint16, uint32, uint64, uintptr uintptr holds a pointer's raw bits
Aliases byte, rune aliases for uint8 and int32
Floating point float32, float64 IEEE 754
Complex complex64, complex128 float32 or float64 real and imaginary parts
var i8 int8 = 127             // -128 to 127
var f64 float64 = 3.141592653589793
var c complex128 = 1 + 2i

s := "こんにちは"
runes := []rune(s) // Unicode code points
bytes := []byte(s) // raw bytes

// rune = int32 alias for a Unicode code point
var ch rune = '🐹'
Enter fullscreen mode Exit fullscreen mode

Conversion and constants

i := 42
f := float64(i)              // Conversions are always explicit; no implicit coercion.
text := fmt.Sprintf("%d", i) // Number to string.
n, err := strconv.Atoi("123") // String to number.

const Pi = 3.14159

// iota: auto-incremented integer constant.
type Weekday int

const (
    Sunday Weekday = iota // 0
    Monday                // 1
    Tuesday               // 2
)

// Bit-flag pattern.
type Permission uint

const (
    Read    Permission = 1 << iota // 1
    Write                          // 2
    Execute                        // 4
)
Enter fullscreen mode Exit fullscreen mode

Pointers

x := 10
p := &x  // Take the address of x.
*p = 20  // Dereference the pointer.
fmt.Println(x) // 20

p1 := new(int) // *int; the pointed-to value is 0.

// Go 1.26+: new accepts an expression as well as a type.
// This does not compile with Go 1.25 or earlier.
p2 := new(42)               // *int; the pointed-to value is 42.
p3 := new(Person{Name: "Alice"}) // *Person initialized with a literal.
Enter fullscreen mode Exit fullscreen mode

Control flow

if

// An initialization statement is scoped to this if/else chain.
if err := doSomething(); err != nil {
    return fmt.Errorf("do something: %w", err)
}

if x > 0 {
    fmt.Println("positive")
} else if x < 0 {
    fmt.Println("negative")
} else {
    fmt.Println("zero")
}
Enter fullscreen mode Exit fullscreen mode

for

// C-style loop.
for i := 0; i < 5; i++ {
}

// Go has no while keyword; use for instead.
for n > 0 {
    n--
}

// Infinite loop.
for {
    if done {
        break
    }
}

// range returns an index and a value for slices and arrays.
for i, v := range []int{10, 20, 30} {
    fmt.Println(i, v) // 0 10 / 1 20 / 2 30
}

// Discard the index.
for _, v := range slice {
}

// Strings range over Unicode code points (runes), not bytes.
for i, r := range "日本語" {
    fmt.Printf("%d: %c\n", i, r)
}

// Maps.
for k, v := range m {
    fmt.Println(k, v) // Iteration order is unspecified.
}
Enter fullscreen mode Exit fullscreen mode

switch

// Expression switch (fallthrough requires an explicit keyword).
switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("macOS")
case "linux":
    fmt.Println("Linux")
default:
    fmt.Println("Other")
}

// Condition-less switch is a cleaner alternative to a long if-else chain.
switch {
case x > 100:
    fmt.Println("big")
case x > 10:
    fmt.Println("medium")
default:
    fmt.Println("small")
}

// Type switch: identify the dynamic type of an interface value.
func describe(i any) {
    switch v := i.(type) {
    case int:
        fmt.Printf("int: %d\n", v)
    case string:
        fmt.Printf("string: %q\n", v)
    default:
        fmt.Printf("unknown: %T\n", v)
    }
}
Enter fullscreen mode Exit fullscreen mode

defer

defer schedules a call to run just before the surrounding function returns. Use it for cleanup: closing files, response bodies, mutexes, and contexts. Deferred calls run in LIFO order.

func writeFile(path string) error {
    f, err := os.Create(path)
    if err != nil {
        return err
    }
    defer f.Close() // Runs when writeFile returns, regardless of how.

    defer fmt.Println("third")
    defer fmt.Println("second")
    defer fmt.Println("first")
    // Prints: first, second, third.
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Note
Deferred call arguments are evaluated when defer is encountered. In a long-running loop, a deferred cleanup does not run until the function returns, so resources accumulate. Close them promptly or move the loop body into a helper function.


Functions

Basic

// Multiple return values.
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

result, err := divide(10, 3)

// Named return values (useful as documentation for short functions).
func minMax(nums []int) (min, max int) {
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    return // Naked return.
}
Enter fullscreen mode Exit fullscreen mode

Variadic functions

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

sum(1, 2, 3)
nums := []int{1, 2, 3}
sum(nums...) // Spread a slice into variadic arguments.
Enter fullscreen mode Exit fullscreen mode

First-class functions and closures

Functions are values in Go: you can assign them to variables, pass them as arguments, and return them. A closure captures variables from its surrounding scope and keeps them alive after the outer function returns.

// Assign a function to a variable.
add := func(a, b int) int { return a + b }

// Pass a function as an argument.
apply := func(f func(int, int) int, x, y int) int {
    return f(x, y)
}
fmt.Println(apply(add, 3, 4)) // 7

// This closure captures n.
func counter() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

c := counter()
c() // 1
c() // 2
Enter fullscreen mode Exit fullscreen mode

init

An init function runs automatically while a package is initialized, before main. A package may have more than one init function. Prefer explicit initialization in main for ordinary application setup; reserve init for package-level registration.

// Runs automatically at package initialization.
func init() {
    // Register a driver or handler.
}
Enter fullscreen mode Exit fullscreen mode

Arrays, slices, and maps

Memory layout

Array (fixed length, value type)
┌───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │  ← contiguous memory (placement decided by the compiler)
└───┴───┴───┴───┴───┘

Slice (variable-length view)
┌────────┬──────┬──────┐
│ ptr    │ len  │ cap  │  ← 3-word slice header
└────┬───┴──────┴──────┘
     ↓
┌───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │   │   │  ← underlying array
└───┴───┴───┴───┴───┴───┘
     ↑           ↑
    len=4       cap=6

Note: actual placement (stack/heap) is decided by escape analysis.
Enter fullscreen mode Exit fullscreen mode

Arrays

Arrays have a fixed length, and that length is part of their type: [3]int and [4]int are different types. Arrays are values, so assigning or passing one copies all its elements.

var a [3]int              // [0 0 0]
b := [3]int{1, 2, 3}
c := [...]int{4, 5, 6}   // Length inferred from the literal.

// Arrays are copied by value.
d := b
d[0] = 99 // b is unchanged.
Enter fullscreen mode Exit fullscreen mode

Slices

A slice is a variable-length view over part or all of an underlying array. Unlike an array, its length is not part of its type. Assigning a slice copies only its header, so two slices may share elements.

// Create a slice.
s := []int{1, 2, 3}
s2 := make([]int, 5)     // len=5, cap=5
s3 := make([]int, 3, 10) // len=3, cap=10

// append may allocate a new backing array when cap is exceeded.
s = append(s, 4, 5)
s = append(s, []int{6, 7}...)

// Slicing shares the backing array.
sub := s[1:3]            // s[1] and s[2]; shares s's array.
limited := s[1:3:3]      // Third index limits the resulting cap.

// copy produces an independent slice.
clone := make([]int, len(s))
copy(clone, s)

// Delete element at index i while preserving order.
i := 2
s = append(s[:i], s[i+1:]...)

fmt.Println(len(s), cap(s))
Enter fullscreen mode Exit fullscreen mode

Caution
A subslice shares its backing array with the original. Mutating one can mutate the other. Use copy when an independent slice is required.

Maps

Maps associate keys with values. Iteration order is unspecified. Keys must be comparable; slices, maps, and functions cannot be map keys.

// Create a map.
m := map[string]int{"a": 1, "b": 2}
m2 := make(map[string]int)

m["c"] = 3
delete(m, "a") // delete is a built-in function.

// Check for key existence.
v, ok := m["missing"] // ok is false when the key is absent.
if ok {
    fmt.Println(v)
}

// Iteration order is unspecified.
for k, v := range m {
    fmt.Println(k, v)
}
Enter fullscreen mode Exit fullscreen mode

Caution
The zero value of a map is nil. Reading from a nil map is safe, but writing to one panics. Initialize it with make or a map literal first.


Structs, methods, and tags

Structs and embedding

type Person struct {
    Name string // Exported: accessible from other packages.
    Age  int
    addr string // Unexported: package-private.
}

// Named-field initialization (preferred).
p1 := Person{Name: "Alice", Age: 30}
p2 := new(Person) // *Person; all fields are zero values.

// Embedding promotes fields and methods.
type Employee struct {
    Person            // Fields and methods of Person are promoted.
    Department string
}

e := Employee{Person: Person{Name: "Carol", Age: 28}, Department: "Eng"}
fmt.Println(e.Name) // Transparent access to Person.Name.
Enter fullscreen mode Exit fullscreen mode

Methods

// Value receiver: p is a copy of the caller.
func (p Person) Greet() string {
    return "Hi, I'm " + p.Name
}

// Pointer receiver: can modify the original Person.
func (p *Person) Birthday() {
    p.Age++
}

p := Person{Name: "Dave", Age: 20}
p.Birthday()       // Go automatically passes &p.
fmt.Println(p.Age) // 21
Enter fullscreen mode Exit fullscreen mode

When to use pointer receivers

  • The method needs to modify the receiver → pointer receiver.
  • The struct is large (avoid copy overhead) → pointer receiver.
  • Otherwise, either works.

Keep receiver kinds consistent across all methods of one type.

Struct tags

Struct tags are metadata attached to fields. The language does not interpret them; packages such as encoding/json and ORMs read them through reflection.

type User struct {
    ID    int    `json:"id" db:"user_id"`
    Email string `json:"email,omitempty"` // Omitted from JSON when empty.
}

// encoding/json reads tags through reflection.
data, _ := json.Marshal(User{ID: 1, Email: ""})
// → {"id":1}   (Email omitted by omitempty)
Enter fullscreen mode Exit fullscreen mode

Interfaces

An interface defines a set of methods. A type satisfies an interface implicitly — no implements keyword. This lets code depend on behavior rather than a concrete type.

interface value layout (the internal representation is not part of the spec)

var s Shape = Circle{Radius: 5}

  s (interface value)
  ┌──────────────┬──────────────┐
  │  type info   │  value/ptr   │
  │  (Circle)    │  {Radius:5}  │
  └──────────────┴──────────────┘

  If either part is nil while the other is not, the interface value itself is not nil.
  (See nil interface trap in Tips.)
Enter fullscreen mode Exit fullscreen mode
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ Radius float64 }

func (c Circle) Area() float64      { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

// Circle implicitly satisfies Shape.
var s Shape = Circle{Radius: 5}
fmt.Println(s.Area())
Enter fullscreen mode Exit fullscreen mode

Type assertions and type switches

// Safe type assertion; the single-result form panics on failure.
c, ok := s.(Circle)
if ok {
    fmt.Println("radius:", c.Radius)
}

// any is an alias for interface{} (Go 1.18+).
func printAny(v any) {
    fmt.Printf("%T: %v\n", v, v)
}
Enter fullscreen mode Exit fullscreen mode

Small interfaces used everywhere

// io.Reader and io.Writer are the most widely used interfaces in the stdlib.
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }

// Implementing fmt.Stringer controls how fmt.Println prints a value.
type Stringer interface { String() string }

func (p Person) String() string {
    return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}

fmt.Println(Person{Name: "Eve", Age: 22}) // Eve (22)
Enter fullscreen mode Exit fullscreen mode

Errors

// error is a built-in interface: type error interface { Error() string }

f, err := os.Open("file.txt")
if err != nil {
    return fmt.Errorf("open file: %w", err) // %w wraps err for errors.Is/As.
}
defer f.Close()
Enter fullscreen mode Exit fullscreen mode

Custom errors

type NotFoundError struct {
    Resource string
    ID       int
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s (id=%d) not found", e.Resource, e.ID)
}

func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, &NotFoundError{Resource: "user", ID: id}
    }
    // ...
    return nil, nil
}
Enter fullscreen mode Exit fullscreen mode

errors package

import "errors"

// Sentinel error for errors.Is comparisons.
var ErrNotFound = errors.New("not found")

func fetchUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("fetchUser: %w", ErrNotFound) // Wrap with %w.
    }
    return nil, nil
}

_, err := fetchUser(-1) // fetchUser returns (*User, error); discard the value.

// errors.Is traverses the error chain to find a matching sentinel.
if errors.Is(err, ErrNotFound) {
    fmt.Println("not found")
}

// errors.As traverses the chain and extracts a value of the target type.
func findItem(id int) error {
    return fmt.Errorf("findItem: %w", &NotFoundError{Resource: "item", ID: id})
}

var nfe *NotFoundError
if errors.As(findItem(42), &nfe) {
    fmt.Println("resource:", nfe.Resource) // "item"
}

// Go 1.26+: a type-safe generic alternative to errors.As.
if nfe, ok := errors.AsType[*NotFoundError](findItem(42)); ok {
    fmt.Println("resource:", nfe.Resource) // "item"
}

// errors.Join combines multiple errors into one (Go 1.20+).
err1 := errors.New("err1")
err2 := errors.New("err2")
combined := errors.Join(err1, err2)
Enter fullscreen mode Exit fullscreen mode

panic and recover

Use panic for broken program invariants or truly unrecoverable situations; return error for expected failures. recover only works when called directly from a deferred function.

func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("must be positive, got %d", n))
    }
    return n
}

func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered: %v", r)
        }
    }()
    return a / b, nil
}
Enter fullscreen mode Exit fullscreen mode

Goroutines, channels, and synchronization

GMP model (Go runtime scheduler)

G = Goroutine (lightweight thread; initial stack ~2 KB, grows as needed)
M = OS thread
P = Processor (controlled by GOMAXPROCS)

  P1                P2
  ┌────────────┐   ┌────────────┐
  │ G1 running │   │ G3 running │
  │ ┌────────┐ │   │ ┌────────┐ │
  │ │ G2     │ │   │ │ G4     │ │ ← local run queue
  │ │ G5     │ │   └────────────┘
  └────────────┘
       M1               M2        ← OS threads
Enter fullscreen mode Exit fullscreen mode

Goroutines

// go keyword: start a function concurrently.
go func() {
    fmt.Println("goroutine")
}()

// Wait for goroutines to finish.
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println(n)
    }(i) // Pass i as an argument to avoid closure capture issues.
}
wg.Wait()
Enter fullscreen mode Exit fullscreen mode

Channels

ch := make(chan int)       // Unbuffered: send blocks until a receiver is ready.
bch := make(chan int, 10)  // Buffered: send blocks only when the buffer is full.

// Unbuffered: send and receive must be in separate goroutines.
go func() { ch <- 42 }()
v := <-ch

// Buffered: send and receive can be in the same goroutine within capacity.
bch <- 1
bch <- 2
fmt.Println(<-bch, <-bch) // 1 2

// Receiving from a closed, drained channel returns (zero, false).
v, ok := <-ch // ok is false when the channel is closed and empty.
Enter fullscreen mode Exit fullscreen mode
// range receives values until the channel is closed and drained.
ch2 := make(chan int, 3)
ch2 <- 1; ch2 <- 2; ch2 <- 3
close(ch2) // The sender closes the channel.
for v := range ch2 {
    fmt.Println(v) // 1, 2, 3
}
Enter fullscreen mode Exit fullscreen mode

Channel direction

// Directional types make intent explicit and catch mistakes at compile time.
func producer(ch chan<- int) { // Send-only.
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch <-chan int) { // Receive-only.
    for v := range ch {
        fmt.Println(v)
    }
}

ch := make(chan int)
go producer(ch)
consumer(ch)
Enter fullscreen mode Exit fullscreen mode

select

// select waits on multiple channel operations simultaneously.
// It picks one ready case at random when several are ready.
select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
case ch3 <- data:
    fmt.Println("sent to ch3")
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("no channel ready") // Non-blocking.
}
Enter fullscreen mode Exit fullscreen mode

sync and atomic

// Mutex: protect shared state from concurrent access.
var mu sync.Mutex
var count int

mu.Lock()
count++
mu.Unlock()

// RWMutex: more efficient when reads are much more frequent than writes.
var rw sync.RWMutex
rw.RLock()  // Multiple goroutines can read simultaneously.
// ... read ...
rw.RUnlock()

// Once: run an initialization function exactly once.
var once sync.Once
once.Do(func() { /* called only the first time */ })

// atomic: low-overhead operations for simple counters and flags.
var n atomic.Int64
n.Add(1)
fmt.Println(n.Load())
Enter fullscreen mode Exit fullscreen mode

Avoid goroutine leaks
When a goroutine sends on a channel, always give it a way to stop if the receiver exits early.

select {
case ch <- result:
case <-ctx.Done(): // Exit when the caller cancels.
    return
}
Enter fullscreen mode Exit fullscreen mode

Context

Context tree

context.Background()
    └── WithCancel → ctx, cancel
            └── WithTimeout(ctx, 5s) → ctx2, cancel2
                    └── WithValue(ctx2, key, val) → ctx3
                                    ↓
                       Cancellation and deadlines propagate to child contexts.
Enter fullscreen mode Exit fullscreen mode

Use context.Context to propagate deadlines, cancellation, and request-scoped metadata. Call the returned cancel function even when a timeout will eventually fire.

import "context"

// Timeout context.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // Always call cancel to release resources.

req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
if err != nil {
    return err
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        fmt.Println("timeout")
    }
    return err
}
defer resp.Body.Close()

// Cancellable context.
ctx2, cancel2 := context.WithCancel(context.Background())
go func() {
    <-ctx2.Done() // Block until cancelled.
    fmt.Println("cancelled:", ctx2.Err())
}()
cancel2()

// context.WithValue carries request-scoped metadata.
// Use it only for small amounts of data; never as a substitute for function parameters.
type ctxKey string
const userKey ctxKey = "user"

ctx3 := context.WithValue(context.Background(), userKey, "alice")
user, ok := ctx3.Value(userKey).(string) // Type-assert with an ok check.
Enter fullscreen mode Exit fullscreen mode

Generics

Go 1.18 introduced type parameters for reusable, type-safe code.

Type parameters

// T and U can be any type.
func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
// [2 4 6]
Enter fullscreen mode Exit fullscreen mode

Type constraints

import "cmp"

// cmp.Ordered allows <, >, ==  (int, float64, string, etc.).
func Min[T cmp.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

Min(3, 5)       // 3 (int)
Min(3.14, 2.71) // 2.71 (float64)
Min("b", "a")   // "a" (string)

// Custom constraint with ~ for underlying types.
type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}
Enter fullscreen mode Exit fullscreen mode

Generic types

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) {
    s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false // Return the zero value of T.
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

s := Stack[string]{}
s.Push("Go")
s.Push("Generics")
v, _ := s.Pop() // "Generics"
Enter fullscreen mode Exit fullscreen mode

Iterators

Since Go 1.23, a function returning iter.Seq can be ranged over directly. When the caller breaks early, yield returns false so the producer can stop.

import "iter"

// Backward yields n-1 through 0.
func Backward(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := n - 1; i >= 0; i-- {
            if !yield(i) { // Caller broke out of the loop.
                return
            }
        }
    }
}

for i := range Backward(3) {
    fmt.Println(i) // 2, 1, 0
}

// maps.Keys returns an iter.Seq; slices.Collect converts it to a slice.
keys := slices.Collect(maps.Keys(m))
Enter fullscreen mode Exit fullscreen mode

Testing

// Place tests in xxx_test.go files.
package mypackage_test

import (
    "testing"

    "github.com/yourname/yourproject/mypackage"
)

func TestAdd(t *testing.T) {
    got := mypackage.Add(2, 3)
    if got != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", got)
    }
}

// Table-driven tests are the idiomatic Go pattern.
func TestDivide(t *testing.T) {
    tests := []struct {
        name    string
        a, b    float64
        want    float64
        wantErr bool
    }{
        {"normal", 10, 2, 5, false},
        {"division by zero", 10, 0, 0, true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := mypackage.Divide(tt.a, tt.b)
            if (err != nil) != tt.wantErr {
                t.Errorf("unexpected error: %v", err)
            }
            if got != tt.want {
                t.Errorf("got %v, want %v", got, tt.want)
            }
        })
    }
}

// Benchmark: run with go test -bench=.
func BenchmarkAdd(b *testing.B) {
    b.ReportAllocs() // Report memory allocations per operation.
    for i := 0; i < b.N; i++ {
        mypackage.Add(2, 3)
    }
}
Enter fullscreen mode Exit fullscreen mode

Test helpers

// t.Helper makes failures report the caller's line, not the helper's.
func requireNoError(t *testing.T, err error) {
    t.Helper()
    if err != nil {
        t.Fatal(err)
    }
}

func TestWriteFile(t *testing.T) {
    t.Setenv("APP_ENV", "test") // Restored automatically after the test.
    dir := t.TempDir()           // Deleted automatically after the test.
    t.Cleanup(func() {
        // Register arbitrary cleanup: reset mocks, close clients, etc.
    })

    path := filepath.Join(dir, "output.txt")
    requireNoError(t, os.WriteFile(path, []byte("hello"), 0o644))
}
Enter fullscreen mode Exit fullscreen mode
go test ./...          # Run all tests.
go test -race ./...    # Detect data races.
go test -cover ./...   # Show coverage.
Enter fullscreen mode Exit fullscreen mode

Standard-library quick reference

fmt

// Output.
fmt.Print("no newline")
fmt.Println("with newline")
fmt.Printf("formatted %s %d %v\n", "str", 42, true)
fmt.Fprintf(os.Stderr, "error: %v\n", err)

// Build strings.
s := fmt.Sprintf("Hello, %s!", name)
msg := fmt.Errorf("wrap: %w", err)

// Common format verbs.
// %v   default             %+v  struct with field names  %#v  Go syntax
// %T   type name           %d   decimal integer          %f   floating-point
// %s   string              %q   quoted string            %x   hexadecimal
// %p   pointer address     %b   binary                   %e   exponent
Enter fullscreen mode Exit fullscreen mode

strings

import "strings"

strings.Contains("Hello", "ell")                    // true
strings.HasPrefix("Hello", "He")                    // true
strings.HasSuffix("Hello", "lo")                    // true
strings.Count("cheese", "e")                        // 3
strings.Index("Hello", "ll")                        // 2
strings.Replace("oink oink", "oink", "moo", 1)     // "moo oink"
strings.ReplaceAll("oink oink", "oink", "moo")      // "moo moo"
strings.ToUpper("hello")                            // "HELLO"
strings.ToLower("HELLO")                            // "hello"
strings.TrimSpace("  hello  ")                      // "hello"
strings.Trim("##hello##", "#")                      // "hello"
strings.Split("a,b,c", ",")                         // ["a" "b" "c"]
strings.Join([]string{"a", "b"}, "-")               // "a-b"
strings.Fields("a  b\tc")                           // ["a" "b" "c"]

// strings.Builder is efficient for repeated concatenation in a loop.
// A plain + is fine for a small number of concatenations.
var sb strings.Builder
for i := 0; i < 5; i++ {
    fmt.Fprintf(&sb, "%d", i)
}
sb.String() // "01234"
Enter fullscreen mode Exit fullscreen mode

strconv

import "strconv"

strconv.Itoa(42)                       // "42"
strconv.Atoi("42")                     // 42, nil
strconv.ParseFloat("3.14", 64)         // 3.14, nil
strconv.FormatFloat(3.14, 'f', 2, 64)  // "3.14"
strconv.ParseBool("true")              // true, nil
strconv.FormatBool(true)               // "true"
Enter fullscreen mode Exit fullscreen mode

sort and slices

import "sort"

nums := []int{5, 2, 4, 1, 3}
sort.Ints(nums)                              // [1 2 3 4 5]

words := []string{"b", "a", "c"}
sort.Strings(words)

// Custom sort.
sort.Slice(nums, func(i, j int) bool {
    return nums[i] > nums[j] // Descending.
})
Enter fullscreen mode Exit fullscreen mode
import (
    "cmp"
    "slices"
)

slices.Sort(nums)                                            // Ascending.
idx, found := slices.BinarySearch(nums, 3)                  // Requires sorted input.

// cmp.Compare avoids integer overflow in comparison functions.
slices.SortFunc(nums, func(a, b int) int { return cmp.Compare(b, a) }) // Descending.
Enter fullscreen mode Exit fullscreen mode

maps (Go 1.21+)

import (
    "maps"
    "slices"
)

m := map[string]int{"a": 1, "b": 2}
m2 := maps.Clone(m)        // Shallow copy.
maps.Copy(m2, m)            // Copy all entries from m into m2.
delete(m, "a")              // delete is a built-in function.
keys := slices.Collect(maps.Keys(m)) // Collect keys into a slice.
Enter fullscreen mode Exit fullscreen mode

time

import "time"

now := time.Now()
t := time.Date(2026, 6, 1, 12, 0, 0, 0, time.Local)

// Go uses a reference time (Mon Jan 2 15:04:05 MST 2006) as the layout.
now.Format("2006-01-02 15:04:05")
now.Format(time.RFC3339)                      // "2026-06-22T12:00:00+09:00"
parsed, _ := time.Parse("2006-01-02", "2026-06-22")

later := now.Add(24 * time.Hour)
diff := later.Sub(now)     // time.Duration
fmt.Println(diff.Hours())  // 24.0

time.Sleep(100 * time.Millisecond)
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
Enter fullscreen mode Exit fullscreen mode

os and filepath

import (
    "os"
    "path/filepath"
)

// File I/O.
data, err := os.ReadFile("file.txt")
err = os.WriteFile("out.txt", data, 0644)

// Directory operations.
entries, _ := os.ReadDir(".")
for _, e := range entries {
    fmt.Println(e.Name(), e.IsDir())
}
os.MkdirAll("a/b/c", 0755)
os.Remove("file.txt")
os.RemoveAll("dir/")

// Environment variables.
home := os.Getenv("HOME")
os.Setenv("KEY", "value")

// Path manipulation.
filepath.Join("a", "b", "c")       // "a/b/c"
filepath.Base("/a/b/file.txt")      // "file.txt"
filepath.Dir("/a/b/file.txt")       // "/a/b"
filepath.Ext("file.txt")            // ".txt"
abs, _ := filepath.Abs("relative")  // Absolute path.
Enter fullscreen mode Exit fullscreen mode

encoding/json

import "encoding/json"

type Point struct {
    X int `json:"x"`
    Y int `json:"y"`
}

// Encode.
data, err := json.Marshal(Point{X: 1, Y: 2})           // []byte
pretty, err := json.MarshalIndent(Point{X: 1}, "", "  ")

// Decode.
var p Point
err = json.Unmarshal(data, &p)

// Streaming.
enc := json.NewEncoder(os.Stdout)
enc.Encode(p)

dec := json.NewDecoder(os.Stdin) // Accepts any io.Reader.
dec.Decode(&p)

// When the type is unknown.
var v any
json.Unmarshal(data, &v)
m := v.(map[string]any)
Enter fullscreen mode Exit fullscreen mode

net/http

import (
    "bytes"
    "context"
    "errors"
    "io"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

// Always set a timeout; the default client has none.
client := &http.Client{Timeout: 10 * time.Second}

// GET request.
resp, err := client.Get("https://example.com")
if err != nil {
    return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)

// Custom request with context.
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonData))
if err != nil {
    return err
}
req.Header.Set("Content-Type", "application/json")
resp2, err := client.Do(req)
if err != nil {
    return err
}
defer resp2.Body.Close()

// HTTP server with timeouts (required in production).
mux := http.NewServeMux()
mux.HandleFunc("GET /hello/{name}", func(w http.ResponseWriter, r *http.Request) {
    name := r.PathValue("name") // Path parameters (Go 1.22+).
    fmt.Fprintf(w, "Hello, %s!", name)
})

server := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       60 * time.Second,
}
go func() {
    if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
        log.Printf("server error: %v", err)
    }
}()

// Graceful shutdown on SIGINT / SIGTERM.
shutdownCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-shutdownCtx.Done()

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
    log.Printf("shutdown error: %v", err)
}
Enter fullscreen mode Exit fullscreen mode

log/slog (Go 1.21+)

import "log/slog"

// Default logger writes text to stderr.
slog.Info("server started", "port", 8080)
slog.Error("request failed", "err", err)

// JSON handler for structured logging (e.g., cloud environments).
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
logger.Info("user logged in", "userID", 42, "ip", "127.0.0.1")
// {"time":"...","level":"INFO","msg":"user logged in","userID":42,"ip":"127.0.0.1"}

// Add fields shared across multiple log calls.
logger.With("requestID", "abc123").Info("processing")
Enter fullscreen mode Exit fullscreen mode

Tips and common mistakes

Goroutine leak

// Bad: the goroutine blocks forever if no one receives from ch.
go func() {
    ch <- result
}()

// Good: always provide a cancellation path.
go func() {
    select {
    case ch <- result:
    case <-ctx.Done():
        return
    }
}()
Enter fullscreen mode Exit fullscreen mode

nil interface trap

// Bad: even when *MyError is nil, the interface value is non-nil.
func getError() error {
    var err *MyError = nil
    return err // The returned error interface is non-nil!
}

if getError() != nil { // true — this branch is taken unexpectedly.
    fmt.Println("this prints unexpectedly")
}

// Good: return nil directly.
func getError() error {
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Pre-allocate slice capacity

// Bad: append may reallocate the backing array many times.
result := []int{}
for i := 0; i < 10000; i++ {
    result = append(result, i)
}

// Good: allocate the full capacity upfront.
result := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
    result = append(result, i)
}
Enter fullscreen mode Exit fullscreen mode

for-loop variable capture (fixed in Go 1.22)

// Go 1.21 and earlier: all goroutines share the same i variable.
for i := 0; i < 5; i++ {
    go func() { fmt.Println(i) }() // May print 5 five times.
}

// Go 1.22+: each iteration creates a new i variable.
// The above code now correctly prints 0, 1, 2, 3, 4 in some order.

// Workaround for Go 1.21 and earlier.
for i := 0; i < 5; i++ {
    i := i // Shadow i with a new variable for this iteration.
    go func() { fmt.Println(i) }()
}
Enter fullscreen mode Exit fullscreen mode

Modules and packages

# Initialize a module.
go mod init github.com/yourname/yourproject

# Go 1.26
go get go@1.26

go get github.com/some/package@v1.2.3   # Add a dependency.
go mod tidy                              # Remove unused dependencies.
go mod vendor                            # Vendor dependencies.
go build ./...                           # Build.
go test ./...                            # Test.
go test -race ./...                      # Detect data races.
go vet ./...                             # Static analysis.
go fmt ./...                             # Format.
Enter fullscreen mode Exit fullscreen mode

Further reading

Top comments (1)

Collapse
 
johnathnwarren profile image
Johnathn Warren

Love it!