🐹 Go

← Back to Cheatsheets

A quick-reference for the most commonly used Go features. Go is a statically typed, compiled language designed for simplicity, concurrency, and fast compile times. Goroutines and channels make concurrent programming idiomatic, and the standard library covers most everyday needs.

Paradigm: Procedural Β· Concurrent (CSP) Typing: Static Β· Strong Execution: Compiled Β· Garbage collected
Resources: go.dev β€” Official site Standard library docs Tour of Go Effective Go Go by Example

Variables & Types

β–Ό

Go is statically typed β€” every variable has a type known at compile time. The short declaration := infers the type; var is required at package level and when zero-initialisation matters.

Short declaration
x := 42
only inside functions; type inferred; cannot redeclare in same scope
Var declaration
var x int = 42
zero-valued if no initializer: 0, "", false, nil; required at package level
Multiple return values
val, err := strconv.Atoi("42")
idiomatic way to return both result and error
Blank identifier
_, err := os.Open("file")
discards a value; Go requires every declared variable to be used
Constants & iota
const (
    A = iota // 0
    B        // 1
    C        // 2
)
iota resets to 0 in each const block; useful for enums

Built-in Functions

β–Ό

Go built-in functions are pre-declared in the universe block and available without imports.

len / cap
len(slice) / cap(slice)
len(m) / len(str)
len returns count of items/bytes; cap returns allocated capacity
make / new
s := make([]int, len, cap)
m := make(map[string]int)
p := new(int) // returns *int pointer to zero value
append / copy
s = append(s, 1, 2)
n := copy(dst, src)
copy returns number of elements copied (min len of dst and src)
delete / clear (Go 1.21+)
delete(m, "key")
clear(slice) // zero-fills slice
clear(m)     // empties map

Loops & Control Flow

β–Ό

Go has a single loop keyword for, covering C-style, while-style (for condition), and infinite loops (for {}). defer runs when the enclosing function returns, in LIFO order.

For / range
for i := 0; i < n; i++ {}        // C-style
for condition {}                  // while-style
for {}                            // infinite loop
for i, v := range slice {}        // index + value
for k, v := range m {}            // map key + value
for _, v := range slice {}        // discard index
range over a string yields runes, not bytes
If with init statement
if err := doWork(); err != nil {
err is scoped to the if/else block β€” keeps error handling local
Switch
switch x {
case 1, 2:
    fmt.Println("one or two")
default:
    fmt.Println("other")
}
no implicit fallthrough; use the fallthrough keyword explicitly if needed
Defer
defer f.Close()
arguments are evaluated immediately; body runs at function exit in LIFO order

String Manipulation

β–Ό

In Go, strings are immutable read-only byte slices ([]byte), typically UTF-8 encoded. Converting to []rune allows character-wise inspection.

Slicing & Runes
s := "Hello, δΈ–η•Œ"
sub := s[0:5]         // "Hello" (byte slicing)
runes := []rune(s)    // unicode code points
char := runes[7]      // 'δΈ–'
len(s) is byte count (13); len([]rune(s)) is character count (9)
Searching & Inspection
strings.Contains(s, "sub")
strings.HasPrefix(s, "pre")
strings.HasSuffix(s, "suf")
strings.Index(s, "x") // -1 if not found
Transformations & Splitting
strings.ToLower(s) / strings.ToUpper(s)
strings.TrimSpace(s) / strings.Trim(s, " ")
parts := strings.Split("a,b,c", ",")
joined := strings.Join(parts, ", ")
Building & Formatting
var b strings.Builder
b.WriteString("hello ")
b.WriteString("world")
res := b.String() // efficient concatenation
formatted := fmt.Sprintf("%s = %d", "x", 42)

Collections & Maps

β–Ό

Sequence β†’ []T (slice) or [N]T (fixed array), Map β†’ map[K]V. Slices are the primary sequence type β€” a view over an underlying array. Maps are hash tables with O(1) average lookup. Both are reference types.

Sequence (slice) β€” literal
s := []int{1, 2, 3}
length and capacity both 3; backed by a new array
Sequence (slice) β€” make
s := make([]int, length, capacity)
pre-allocates backing array; avoids repeated copies on append
Sequence (slice) β€” append
s = append(s, 4, 5)
s = append(s, other...)
must reassign; append may return new slice if capacity exceeded
Sequence (slice) β€” slice
s[low:high]
shares the backing array; mutations affect the original; high is exclusive
Map β€” literal
m := map[string]int{"a": 1}
Map β€” make
m := make(map[string]int)
a nil map panics on write; always initialise with make or a literal
Map β€” comma-ok idiom
v, ok := m["key"]
ok is false if key is absent; v is the zero value β€” always check ok
Map β€” delete
delete(m, "key")
safe to call even if the key does not exist

Data Type Interaction Matrix

Interaction []T (slice) [N]T (array) map[K]V string struct
Access item s[i] arr[i] m[k] / v, ok := m[k] s[i] (byte) st.Field
Access last item s[len(s)-1] arr[len(arr)-1] Unordered s[len(s)-1] N/A (named fields)
Add / Append s = append(s, x) N/A (fixed size) m[key] = val s += "x" N/A (fixed fields)
Insert at index append(s[:i], append([]T{x}, s[i:]...)...) N/A N/A (by key) s[:i] + "x" + s[i:] N/A
Update item s[i] = val arr[i] = val m[k] = val Immutable (strings.Replace) st.Field = val
Update last item s[len(s)-1] = val arr[len(arr)-1] = val Unordered s[:len(s)-1] + "c" N/A
Remove item append(s[:i], s[i+1:]...) N/A delete(m, k) strings.Replace(s, sub, "", 1) N/A
Pop (remove & get) x, s = s[len(s)-1], s[:len(s)-1] N/A v := m[k]; delete(m, k) c, s = s[len(s)-1], s[:len(s)-1] N/A
Check existence slices.Contains(s, x) (O(n)) slices.Contains(arr, x) (O(n)) _, ok := m[k] (O(1)) strings.Contains(s, sub) Compile time check
Clear / Empty s = s[:0] / s = nil arr = [N]T{} clear(m) / m = make(...) s = "" st = Struct{}
Mutable in-place? Yes Yes Yes No (Immutable) Yes
Ordered? Yes (0-indexed) Yes (0-indexed) No (Unordered) Yes (0-indexed) N/A (Named fields)

Functions

β–Ό

Functions are first-class values. Multiple return values replace exceptions for expected errors. Closures capture variables by reference.

Named return values
func divide(a, b float64) (result float64, err error) {
naked return returns named values; useful for short functions
Variadic
func sum(nums ...int) int {
sum(1, 2, 3)
sum(nums...)
nums is a slice inside the function; spread with ...
First-class functions
fn := func(x int) int { return x * 2 }
function types are comparable; useful for callbacks and strategy pattern
Closures
adder := func(x int) func(int) int {
    return func(y int) int { return x + y }
}
add5 := adder(5)
add5(3) // 8
x is captured by reference β€” mutations in the closure affect it

Structs & Interfaces

β–Ό

Go uses composition over inheritance. Interfaces are satisfied implicitly β€” any type implementing the required methods qualifies. Use pointer receivers when the method needs to mutate the struct.

Struct
type Point struct {
    X, Y float64
}
p := Point{X: 1.0, Y: 2.0}
p.X = 3.0
unset fields are zero-valued; use field names in literals for clarity
Methods (value vs pointer receiver)
func (p Point) Dist() float64 {}
func (p *Point) Scale(f float64) {}
value receiver = copy; pointer receiver = mutates original; be consistent on a type
Interface
type Stringer interface {
    String() string
}
// any type with String() string satisfies this
// no "implements" keyword needed
Type assertion
s, ok := val.(string)
ok is false if assertion fails; without ok it panics on mismatch
Type switch
switch v := i.(type) {
case string:
    fmt.Println("string:", v)
case int:
    fmt.Println("int:", v)
default:
    fmt.Printf("unknown: %T\n", v)
}

Error Handling

β–Ό

In Go, errors are values β€” functions return them as a second return value and callers check immediately. Use %w to wrap errors so callers can inspect the cause with errors.Is and errors.As.

Error check pattern
result, err := doSomething()
if err != nil {
    return fmt.Errorf("context: %w", err)
}
%w wraps the original error; %v includes it in the message but doesn't wrap
errors.Is / errors.As
errors.Is(err, os.ErrNotExist)
errors.As(err, &target)
both unwrap the error chain; Is checks identity, As extracts a typed value
Custom error type
type NotFoundError struct{ Name string }

func (e *NotFoundError) Error() string {
    return e.Name + " not found"
}
implement the error interface by defining Error() string

Goroutines & Channels

β–Ό

Goroutines are lightweight threads managed by the Go runtime β€” run thousands concurrently. Channels let goroutines communicate safely. "Do not communicate by sharing memory; share memory by communicating."

Goroutine
go func() { ... }()
starts a new goroutine; the calling goroutine continues immediately
Channel
ch := make(chan int)
ch <- 42      // send
v := <-ch     // receive
unbuffered: send blocks until receiver is ready
Buffered channel
ch := make(chan int, 10)
send blocks only when buffer is full; useful for decoupling producers and consumers
Select
select {
case v := <-ch1:
    fmt.Println("received", v)
case ch2 <- x:
    fmt.Println("sent")
default:
    fmt.Println("no channel ready")
}
picks a ready case at random if multiple are ready; default makes it non-blocking
sync.WaitGroup
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    doWork()
}()
wg.Wait()
Add before the goroutine starts; Done via defer to handle panics
sync.Mutex
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
// critical section
protect shared state; defer unlock ensures release even on panic

Common Patterns

β–Ό

Idiomatic Go patterns that appear in almost every real-world codebase.

Error wrapping
if err != nil {
    return fmt.Errorf("open config: %w", err)
}

// caller can inspect
errors.Is(err, os.ErrNotExist)
var e *PathError
errors.As(err, &e)
%w wraps for errors.Is/As; %v just includes the message without wrapping
Functional options
type Option func(*Server)

func WithTimeout(d time.Duration) Option {
    return func(s *Server) { s.timeout = d }
}

s := NewServer(WithTimeout(5 * time.Second))
cleaner than a config struct when options are few and optional
sync.Once β€” init once
var (
    instance *DB
    once     sync.Once
)
func GetDB() *DB {
    once.Do(func() { instance = connect() })
    return instance
}
safe singleton; Do executes the function exactly once even under concurrency
context.WithTimeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := db.QueryContext(ctx, query)
always defer cancel() to avoid context leaks; propagate ctx through call chain
Table-driven tests
func TestAdd(t *testing.T) {
    tests := []struct{ a, b, want int }{
        {1, 2, 3}, {0, 0, 0}, {-1, 1, 0},
    }
    for _, tt := range tests {
        if got := Add(tt.a, tt.b); got != tt.want {
            t.Errorf("Add(%d,%d) = %d; want %d", tt.a, tt.b, got, tt.want)
        }
    }
}
idiomatic Go test style; add t.Parallel() inside the loop for parallel subtests

Common stdlib

β–Ό

The Go standard library covers HTTP, JSON, file I/O, string manipulation, cryptography β€” most everyday tasks need no external packages.

fmt verbs
fmt.Sprintf("%s=%d", k, v)
fmt.Fprintf(os.Stderr, "err: %v\n", err)
%v = default, %+v = with field names, %#v = Go syntax, %T = type
strings package
strings.Contains / HasPrefix / HasSuffix
strings.Split / Join / TrimSpace / ToLower
strings.Builder // efficient concatenation
strings.Builder avoids O(nΒ²) cost of repeated + concatenation
strconv
strconv.Itoa(42)
strconv.Atoi("42")
strconv.FormatFloat(f, 'f', 2, 64)
Atoi returns (int, error); ParseInt/ParseFloat give more control
os.Args / os.Exit / os.Getenv
os.Args[1:]
os.Exit(1)
val := os.Getenv("HOME")
os.Exit skips deferred functions β€” prefer returning an error when possible
log.Fatal / log.Printf
log.Fatalf("open %s: %v", path, err)
Fatal calls os.Exit(1) after logging; use log/slog for structured logging