Go Cheatsheet

Goroutines

Use this Go reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

What Is a Goroutine

A goroutine is a lightweight thread managed by the Go runtime. Goroutines are multiplexed onto OS threads; creating thousands is normal.

func work(id int) {
    fmt.Printf("worker %d done\n", id)
}

func main() {
    go work(1)           // launch goroutine
    go work(2)
    go func() {         // anonymous goroutine
        fmt.Println("anonymous")
    }()
    time.Sleep(100 * time.Millisecond)  // crude wait (use WaitGroup in real code)
}

go precedes any function call. The call returns immediately; the goroutine runs concurrently.

sync.WaitGroup

The standard way to wait for a collection of goroutines to finish.

import "sync"

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)             // increment before launching
    go func(id int) {
        defer wg.Done()   // decrement when goroutine exits
        fmt.Println("worker", id)
    }(i)
}

wg.Wait()  // block until counter reaches 0

Common mistake: calling wg.Add(1) inside the goroutine — a race between the launch and the wait.

sync.Mutex

Protects shared data from concurrent access. Zero value is an unlocked mutex.

import "sync"

type SafeCounter struct {
    mu sync.Mutex
    n  int
}

func (c *SafeCounter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.n++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.n
}

c := &SafeCounter{}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
    wg.Add(1)
    go func() { defer wg.Done(); c.Inc() }()
}
wg.Wait()
fmt.Println(c.Value())  // 1000

sync.RWMutex

Multiple concurrent readers OR one exclusive writer.

type Cache struct {
    mu    sync.RWMutex
    store map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.store[key]
    return v, ok
}

func (c *Cache) Set(key, val string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.store[key] = val
}

sync.Once

Runs a function exactly once, regardless of how many goroutines call it. Used for lazy initialization.

var (
    instance *DB
    once     sync.Once
)

func GetDB() *DB {
    once.Do(func() {
        instance = openDB()
    })
    return instance
}

sync.Map

Concurrent map with no external locking needed. Preferred for read-heavy or key-stable workloads.

var sm sync.Map

sm.Store("key", 42)

v, ok := sm.Load("key")        // (42, true)
sm.LoadOrStore("key", 99)      // returns existing (42, true)
sm.Delete("key")

sm.Range(func(k, v any) bool {
    fmt.Println(k, v)
    return true  // return false to stop
})

sync.Cond

Condition variable for signaling between goroutines.

var mu sync.Mutex
cond := sync.NewCond(&mu)
ready := false

// Consumer
go func() {
    mu.Lock()
    for !ready {
        cond.Wait()  // atomically releases mu and suspends
    }
    mu.Unlock()
    fmt.Println("ready!")
}()

// Producer
mu.Lock()
ready = true
cond.Signal()   // wake one waiter (or cond.Broadcast() for all)
mu.Unlock()

atomic Package

Lock-free operations on primitive values. Faster than a mutex for simple counters/flags.

import "sync/atomic"

var counter int64

// Increment
atomic.AddInt64(&counter, 1)

// Load / Store
v := atomic.LoadInt64(&counter)
atomic.StoreInt64(&counter, 0)

// Compare-and-swap
swapped := atomic.CompareAndSwapInt64(&counter, 0, 1)

// Atomic value (any type)
var val atomic.Value
val.Store(myStruct{})
v2 := val.Load().(myStruct)

// Go 1.19+ typed atomics
var cnt atomic.Int64
cnt.Add(1)
cnt.Load()
cnt.Store(0)
cnt.Swap(5)
cnt.CompareAndSwap(5, 10)

var flag atomic.Bool
flag.Store(true)
flag.Load()

Goroutine Patterns

Fan-out (scatter work)

results := make([]chan int, 10)
for i := range results {
    ch := make(chan int, 1)
    results[i] = ch
    go func(id int, out chan<- int) {
        out <- heavyWork(id)
    }(i, ch)
}
for _, ch := range results {
    fmt.Println(<-ch)
}

Worker pool

jobs := make(chan int, 100)
var wg sync.WaitGroup

// Start N workers
for w := 0; w < 5; w++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        for j := range jobs {
            process(j)
        }
    }()
}

// Send jobs
for j := 0; j < 100; j++ {
    jobs <- j
}
close(jobs)  // signal workers to stop
wg.Wait()

Pipeline

func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

for v := range square(generate(2, 3, 4)) {
    fmt.Println(v)   // 4, 9, 16
}

Context-based cancellation

import "context"

func longOp(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()  // context.Canceled or DeadlineExceeded
        default:
            // do work
        }
    }
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := longOp(ctx)

GOMAXPROCS

Controls the number of OS threads executing goroutines in parallel. Defaults to the number of CPU cores.

import "runtime"

runtime.GOMAXPROCS(runtime.NumCPU())  // already the default
prev := runtime.GOMAXPROCS(1)         // single-threaded
fmt.Println("CPUs:", runtime.NumCPU())
fmt.Println("goroutines:", runtime.NumGoroutine())

Race Detector

Run with -race to detect data races at runtime.

go run -race main.go
go test -race ./...
go build -race -o myapp

Common Gotchas

// 1. Loop variable capture (Go < 1.22)
for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i)  // RACE: all goroutines may see i=5
    }()
}
// Fix: shadow or pass as argument
for i := 0; i < 5; i++ {
    i := i  // shadow
    go func() { fmt.Println(i) }()
}
// In Go 1.22+ loop variables are per-iteration — no fix needed

// 2. Goroutine leak: always ensure goroutines can exit
// Use context cancellation or a done channel

// 3. Goroutines are not threads — you can have millions
// but blocking syscalls tie up an OS thread

// 4. defer wg.Done() in goroutines prevents missed Done() on panic
go func() {
    defer wg.Done()
    // ...
}()