DS DevShelfHub Projects · AI tools
Cheatsheet · Languages

Go Cheatsheet: Goroutines, Channels and Interfaces Reference

By DevShelfHub

Types, slices, maps, structs, interfaces, goroutines, channels, errors, generics, modules — the modern Go (1.22+) surface day-to-day.

115 items ◷ 8 min Goroutines Channels Interfaces

Start hereQuick start · 6 you’ll reach for daily

Rungo run ./...
Buildgo build -o bin/app ./cmd/app
Testgo test ./...
Concurrencygo fn() / ch <- v
Error wrapfmt.Errorf("…: %w", err)
Modulego mod tidy

Target versions · paceVersions

Targets: go ≥ 1.22 generics (1.18+) log/slog · http.ServeMux verbs (1.22) range-over-func (1.23)

Go ships a new minor every six months and the language is stable — old code keeps compiling. Recent additions worth using: generics (1.18), structured logging via log/slog (1.21), HTTP method routing + path variables in net/http (1.22), and range-over-func / iterators (1.23). GOPATH-style projects are dead — everything new is a module.

Install · modulesSetup

bash
# Install (macOS / Linux). Prefer the official tarball; package managers lag.
brew install go                     # macOS
sudo apt install golang-go          # Debian/Ubuntu

# Or pin a version with the go command itself
go install golang.org/dl/go1.22@latest
go1.22 download

# Project setup — modules, no GOPATH needed
mkdir myapp && cd myapp
go mod init github.com/me/myapp
go get github.com/spf13/cobra@latest
go mod tidy                         # prune unused, add missing

# Daily commands
go run ./...                        # run all packages
go build -o bin/app ./cmd/app       # build a binary
go test ./...                       # run all tests
go vet ./... && gofmt -w .          # static checks + format

Where things liveStandard library map

Go’s stdlib is unusually rich. Reach for it before the ecosystem — you almost never need a framework for HTTP, JSON, or testing.

fmtFormatted I/O: Println, Sprintf, Errorf.
errorsIs / As / Join / New.
log/slogPreferred Structured logging. Replaces log for new code.
io, io/fsReader/Writer interfaces. Build composable streams.
os, os/exec, os/signalFiles, env, child processes, signals.
net/httpProduction-grade HTTP client + server.
encoding/jsonJSON via struct tags. Use json.Encoder for streaming.
contextDeadlines, cancellation, request-scoped values.
sync, sync/atomicMutex, WaitGroup, Once, atomic ops.
timeTime, Duration, Ticker, Timer.
slices, maps, cmpGeneric collection helpers (1.21+).
testing, testing/quick, testing/iotestBuilt-in unit / fuzz / property tests.

Variables · control flowSyntax

Declarations

var x int = 5Full form. Rare in function bodies.
x := 5Short form. Only inside functions.
x, y := 1, "hi"Multiple. Types inferred.
const Pi = 3.14159Compile-time constant. Untyped until assigned.
var ( a int; b string )Grouped var. Convention at package scope.
_ = exprBlank identifier. Discards a return value.

Control flow

if x > 0 { … } else if … { … }Braces required. No parens.
if v, err := fn(); err != nil { … }Init statement scoped to the if.
for i := 0; i < 10; i++C-style.
for cond { … }While loop.
for { … }Infinite loop. break exits.
for i, v := range xsRange over slice / array / string / map / channel.
for v := range iter (1.23+)Range over a custom iterator function.
switch x { case 1: … default: … }No fallthrough by default. Use fallthrough explicitly.
switch v := x.(type) { case int: … }Type switch on an interface value.

Built-ins · compositesTypes

Built-in types

int / int32 / int64 / uint / uintptrSized + machine-word. int is 64-bit on most platforms.
float32 / float64IEEE-754. Prefer float64.
bool, string, byte (uint8), rune (int32)Booleans, immutable strings, ASCII byte, Unicode codepoint.
complex64 / complex128Built-in complex numbers.
any (= interface{})Preferred Alias added in 1.18.

Strings & runes

s := "hëllo"UTF-8 immutable.
len(s)Byte length, not rune count.
utf8.RuneCountInString(s)Number of Unicode code points.
s[i]A byte. Use []rune(s)[i] for codepoints.
strings.Join, Split, Contains, HasPrefix, ReplaceAllDaily stdlib helpers.
strconv.Atoi / Itoa / ParseFloat / FormatFloatString ↔ number conversions.

Composite

[5]int{1,2,3,4,5}Array. Fixed size, value type.
[]int{1,2,3}Slice literal. The everyday list type.
make([]int, 0, 16)Slice with capacity. Use to avoid reallocs.
map[string]int{"a":1}Hash map. Random iteration order.
make(map[string]int, 64)Pre-size the map.
type Point struct { X, Y int }Struct declaration.
p := Point{X:1, Y:2}Composite literal. Field names recommended.
type Celsius float64Named type — new type, not just alias.
type Celsius = float64Type alias (note the =).

The collection trioSlices, maps, pointers

Slices

xs := []int{1,2,3}Literal.
xs = append(xs, 4)Returns a new header. Always assign.
xs[1:3]Slice expression. Shares backing array.
copy(dst, src)Element-wise copy. Returns bytes copied.
len(xs), cap(xs)Length vs allocated capacity.
xs = slices.Delete(xs, i, j)Generic helper for in-place delete.
slices.Sort(xs)In-place sort for ordered types.
slices.SortFunc(xs, fn)Sort with a comparator.

Maps

m["k"] = 1Set. Map must be non-nil.
v, ok := m["k"]Comma-ok idiom: distinguish missing from zero.
delete(m, "k")Remove a key.
for k, v := range mIterate. Random order each run.
clear(m)Built-in, empties the map (1.21+).
maps.Keys(m), maps.Values(m)Iterators (1.23+) over keys / values.

Pointers

p := &xAddress-of.
*p = 5Dereference and assign.
var p *TNil pointer (zero value).
new(T)Allocate zeroed T, return pointer.
No pointer arithmeticBy design. Use slices for indexing.

Methods · embeddingStructs & interfaces

Methods

func (p Point) Mag() float64Value receiver. p is a copy.
func (p *Point) Scale(k float64)Pointer receiver. Mutates the original.
Receiver consistencyPick value or pointer for a type; don’t mix.
type Inner struct{}; type Outer struct{ Inner }Embedding. Outer gets Inner’s methods.

Interfaces

type Reader interface { Read(p []byte) (int, error) }Behavior set. Methods only.
Implicit satisfactionAny type with the methods is a Reader. No declaration needed.
var r io.Reader = &myReader{}Assignment proves it implements.
v, ok := i.(*Concrete)Type assertion with comma-ok.
switch v := i.(type) { case A: … case B: … }Type switch.
any = interface{}Empty interface alias. Matches anything.
Small interfaces > big interfacesIdiomatic: 1–3 methods.

Goroutines · channels · selectConcurrency

Goroutines

go fn(arg)Launch a goroutine. Returns immediately.
go func(){ … }()Inline anonymous form. Mind variable capture.
runtime.NumGoroutine()Live count. Watch for leaks.

Channels

ch := make(chan int)Unbuffered. Send blocks until receive.
ch := make(chan int, 8)Buffered. Send blocks only when full.
ch <- vSend.
v := <-chReceive.
v, ok := <-chok is false when ch is closed and drained.
close(ch)Signal "no more values". Sending to a closed channel panics.
for v := range chReceive until close.
chan<- T / <-chan TSend-only / receive-only types. Use in signatures.

select & sync

select { case v := <-a: … case b <- 1: … default: … }Wait on multiple channels. default is non-blocking.
case <-time.After(2*time.Second)Per-iteration timeout.
case <-ctx.Done()Cancellation arm. Always include in long-running loops.
sync.Mutex { Lock, Unlock }Mutual exclusion. defer m.Unlock().
sync.WaitGroup { Add, Done, Wait }Wait for N goroutines.
sync.Once.Do(fn)Run once across goroutines. Idiomatic singleton init.
errgroup.Group.Go / WaitConcurrent fan-out with first-error semantics.

Worked example

go
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for j := range jobs {            // ranges until jobs is closed
        time.Sleep(50 * time.Millisecond)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 5)        // buffered → non-blocking up to 5
    results := make(chan int, 5)
    var wg sync.WaitGroup

    for w := 1; w <= 3; w++ {        // fan out 3 workers
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)                       // signal "no more work"

    go func() { wg.Wait(); close(results) }()

    for r := range results {
        fmt.Println(r)
    }
}

Wrap · Is · AsErrors

errors.New("…")Sentinel error value.
fmt.Errorf("ctx: %w", err)Preferred Wrap with context. %w preserves the chain.
errors.Is(err, target)Sentinel comparison through the chain.
errors.As(err, &ptr)Walk the chain for a typed error.
errors.Join(e1, e2)Combine multiple errors into one.
defer func(){ if r := recover(); r != nil { … } }()Catch a panic. Last resort, not flow control.
return nil instead of empty structIdiomatic "no error".

Worked example

go
package main

import (
    "errors"
    "fmt"
    "os"
)

// Custom error with structured context
type NotFoundError struct {
    Resource string
    ID       string
}

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

// Wrapping: %w preserves the chain so errors.Is / errors.As can unwrap
func loadUser(id string) error {
    _, err := os.ReadFile("users/" + id + ".json")
    if err != nil {
        return fmt.Errorf("loadUser(%s): %w", id, err)
    }
    return nil
}

func main() {
    err := loadUser("u_42")

    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("file missing — fall back to cache")
    }

    var nfe *NotFoundError
    if errors.As(err, &nfe) {
        fmt.Printf("typed error: %+v\n", nfe)
    }
}

Type parametersGenerics

func Map[T, U any](xs []T, fn func(T) U) []UGeneric function. [T, U any] = type params.
type Stack[T any] struct { xs []T }Generic type.
cmp.OrderedBuilt-in constraint: types with <.
type Numeric interface { int | float64 | … }Union constraint. Pipe-separated types.
~intAllow any type whose underlying type is int.
slices.Index, slices.Contains, slices.SortGeneric stdlib helpers. Use these.
No method type parametersMethods can’t introduce new type params — only the receiver type can.

Worked example

go
package main

import (
    "cmp"
    "fmt"
    "slices"
)

// Generic function — works for any ordered type
func Max[T cmp.Ordered](xs []T) T {
    m := xs[0]
    for _, x := range xs[1:] {
        if x > m { m = x }
        }
    return m
}

// Generic constraint with a method set
type Stringer interface { String() string }

func Join[T Stringer](xs []T, sep string) string {
    out := ""
    for i, x := range xs {
        if i > 0 { out += sep }
        out += x.String()
    }
    return out
}

func main() {
    fmt.Println(Max([]int{3, 7, 2}))            // 7
    fmt.Println(Max([]string{"go", "is", "fun"})) // "is"

    nums := []int{5, 1, 4, 2, 3}
    slices.Sort(nums)                            // generic stdlib
    fmt.Println(slices.Contains(nums, 4))         // true
}

Dependency · toolchainModules & tools

go mod init <path>Create go.mod. Path is the import path.
go get pkg@v1.2.3Add or upgrade a dependency. Version is optional.
go mod tidyAdd missing, drop unused. Run before commit.
go mod vendorCopy deps into vendor/. Optional, useful for builds offline.
go work init & go work use ./mod-a ./mod-bMulti-module workspace.
go build / go installCompile / install to $GOBIN.
go run ./cmd/appCompile + run.
go test -race -run TestX ./pkgRace detector + test filter.
go test -bench=. -benchmemRun benchmarks. Watch allocs.
go vet ./...Static checks for common mistakes.
gofmt -w . / go fmt ./...Format. Non-negotiable, no config.
go generate ./...Run //go:generate directives.

HTTP service · ~45 linesEnd-to-end · HTTP service

Go 1.22 ServeMux with method routing + path variables, structured logging via slog, graceful shutdown on SIGINT / SIGTERM. No framework needed.

go
package main

import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

type Greeting struct {
    Name    string `json:"name"`
    Message string `json:"message"`
}

func handleHello(w http.ResponseWriter, r *http.Request) {
    name := r.PathValue("name")                  // Go 1.22 path params
    g := Greeting{Name: name, Message: "hi " + name}
    w.Header().Set("Content-Type", "application/json")
    _ = json.NewEncoder(w).Encode(g)
}

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    slog.SetDefault(logger)

    mux := http.NewServeMux()
    mux.HandleFunc("GET /hello/{name}", handleHello)

    srv := &http.Server{Addr: ":8080", Handler: mux}

    go func() {
        slog.Info("listening", "addr", srv.Addr)
        if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
            slog.Error("listen", "err", err); os.Exit(1)
        }
    }()

    // Graceful shutdown on SIGINT / SIGTERM
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
    <-stop

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        fmt.Println(err)
    }
}

Best practiceGood to know

Always check the second return value. Go’s idiom is paired returns — v, err := f() and v, ok := m["k"]. Ignoring either is the most common source of nil deref and silent missing keys.
Pass context.Context as the first argument. Any function that does I/O, blocks, or could be canceled should take ctx and check <-ctx.Done(). Skip it only for pure CPU functions.
Don’t over-design with interfaces. Define an interface where it’s consumed, not where it’s implemented. One- or two-method interfaces (io.Reader, io.Writer) compose; large ones don’t.

Common trapsWatch out for

Variable capture in loops — fixed in 1.22. Before 1.22, for _, v := range xs { go fn(v) } would close over the same v across iterations. 1.22 per-iteration scopes the variable. Old codebases still have the trap.
Append can alias. b := append(a, x) may return a slice sharing a’s backing array. Mutating b then touches a. Use slices.Clone when you need independence.
Nil interface != nil concrete pointer. A var p *T wrapped in an interface is non-nil even though p == nil. Returning a typed nil from a function returning error hides bugs.

Go deeperSee also

Go FAQ

What is Go used for?

Go (Golang) is a statically typed, compiled language designed for simplicity, fast builds, and efficient concurrency. It is widely used for cloud infrastructure, CLI tools, microservices, network servers, and DevOps tooling. Notable projects built in Go include Docker, Kubernetes, Terraform, and CockroachDB.

What is the difference between a goroutine and a thread?

A goroutine is a lightweight, user-space concurrent function managed by the Go runtime — you can run millions of them. OS threads are much heavier (1 MB+ stack, kernel scheduling). The Go runtime multiplexes goroutines onto a small number of OS threads (GOMAXPROCS, defaulting to the number of CPUs). Start a goroutine with go fn().

How do channels work in Go?

Channels are typed conduits for sending values between goroutines: ch := make(chan int) creates an unbuffered channel, and make(chan int, 10) creates a buffered one with capacity 10. Send with ch <- v (blocks until a receiver is ready on unbuffered), receive with v := <-ch. Close a channel with close(ch) to signal completion to range loops.

What are interfaces in Go?

An interface is a set of method signatures. Any type that implements all the methods implicitly satisfies the interface — there is no implements keyword. This implicit satisfaction makes Go interfaces lightweight and composable. The io.Reader and io.Writer interfaces from the standard library are the canonical examples.

How does error handling work in Go?

Go functions return errors as explicit return values: func f() (result, error). Check with if err != nil. Wrap errors to add context using fmt.Errorf('doing X: %w', err), and unwrap with errors.Is or errors.As. There is no exception mechanism — panics are reserved for unrecoverable situations.

Is Go open source and free?

Yes. Go is BSD-licensed and developed by Google with an active open-source community. The compiler, standard library, and toolchain are all free. The official Go installer from go.dev is the standard distribution; no commercial licence is required.