Go
SystemsOverview
Statically typed, compiled language designed at Google. Known for simplicity, fast compilation, built-in concurrency, and excellent tooling.
Resources
Popular learning and reference links:
Installation & Getting Started
Download from the official site or use a package manager. Go is a single binary with everything included.
# macOS (Homebrew)
brew install go
# Linux
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
# Windows — download installer from https://go.dev/dl/
# Verify installation
go version
# No REPL built-in — but alternatives exist:
# gore — a Go REPL
go install github.com/x-motemen/gore/cmd/gore@latest
gore
# Go Playground — try Go in the browser
# https://go.dev/play/
# Run a file directly (no build step needed)
go run main.go
# Quick one-liner (via go run)
echo 'package main; import "fmt"; func main() { fmt.Println("Hello!") }' > /tmp/hello.go && go run /tmp/hello.go Project Scaffolding
Initialize a module with go mod init. No external scaffolding tools needed.
# Create a new project
mkdir my-project && cd my-project
go mod init github.com/user/my-project
# Create main file
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
EOF
# Run the project
go run .
# Build a binary
go build -o my-app . Package Management
Go modules (go mod) is the built-in dependency management system.
# Add a dependency
go get github.com/gin-gonic/gin
# Add a specific version
go get github.com/gin-gonic/gin@v1.9.1
# Tidy up (remove unused, add missing)
go mod tidy
# List dependencies
go list -m all
# Update all dependencies
go get -u ./...
# Vendor dependencies
go mod vendor Tooling & Formatter/Linter
Go has strong built-in tooling. Formatting is enforced by convention — all Go code looks the same.
# gofmt — built-in formatter (canonical style)
gofmt -w .
# goimports — gofmt + auto-manage imports
go install golang.org/x/tools/cmd/goimports@latest
goimports -w .
# go vet — built-in static analysis
go vet ./...
# golangci-lint — meta-linter (runs many linters)
# Install: https://golangci-lint.run/usage/install/
golangci-lint run
# staticcheck — advanced static analysis
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
# .golangci.yml
linters:
enable:
- errcheck
- govet
- staticcheck
- unused
- gosimple
- ineffassign
- misspell
- revive
# Editor: gopls (Go language server) provides
# formatting, linting, and refactoring in all editors Build & Compile Model
Ahead-of-time compiled to native binary. Go compiles directly to machine code. No VM, no runtime dependency.
# Compile and run
go run main.go # Compile + run in one step
go build -o myapp . # Compile to binary
./myapp # Run the binary
# Release build
go build -ldflags="-s -w" -o myapp . # Strip debug info
# Cross-compilation (built-in!)
GOOS=linux GOARCH=amd64 go build -o myapp-linux .
GOOS=darwin GOARCH=arm64 go build -o myapp-mac .
GOOS=windows GOARCH=amd64 go build -o myapp.exe .
# Output: single static binary (no dependencies)
Execution model:
- Source → AST → SSA IR → Machine code (single binary)
- Compilation is extremely fast (seconds for large projects)
- Statically linked by default — one binary, zero dependencies
- Includes a small runtime (goroutine scheduler, GC) in the binary
- No JIT — all optimization happens at compile time
Libraries & Frameworks
Go has a rich standard library and a growing ecosystem focused on cloud, networking, and backend services.
Web Frameworks - Gin, Echo, Fiber, Chi, net/http (stdlib)
API / RPC - gRPC, Connect, Twirp, OpenAPI generators
Databases - GORM, sqlx, pgx, ent, database/sql (stdlib)
Cloud / Infra - Docker, Kubernetes client-go, Terraform Plugin Framework, AWS SDK
CLI - Cobra, urfave/cli, Bubble Tea (TUI), Charm tools
Logging - slog (stdlib), Zap, zerolog, logrus
Config - Viper, envconfig, koanf
Testing - testify, gomock, ginkgo, go-cmp
Concurrency - errgroup, semaphore (x/sync), conc
Serialization - encoding/json (stdlib), json-iterator, protobuf
HTTP Clients - net/http (stdlib), resty, req
Testing
Go has a built-in testing framework in the testing package. No external dependencies needed. testify is a popular assertion library.
// math_test.go — test files end in _test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Add(1, 2) = %d; want 3", result)
}
}
// Table-driven tests (idiomatic Go pattern)
func TestAddTable(t *testing.T) {
tests := []struct {
a, b, want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tt := range tests {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d, want %d",
tt.a, tt.b, got, tt.want)
}
}
}
// Benchmarks
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
# Run tests
go test ./... # All packages
go test -v ./... # Verbose
go test -run TestAdd # Specific test
go test -bench . # Run benchmarks
go test -cover # With coverage
go test -race ./... # Race condition detection Debugging
Delve is the standard Go debugger. Built-in log and fmt for print debugging. VS Code and GoLand have GUI support.
// Print debugging
fmt.Println("debug:", value)
fmt.Printf("type: %T, value: %+v\n", obj, obj)
// log package — timestamped output
import "log"
log.Println("starting server")
log.Printf("request: %+v", req)
log.Fatal("cannot start") // Logs then os.Exit(1)
// slog — structured logging (Go 1.21+)
import "log/slog"
slog.Info("request", "method", r.Method, "path", r.URL.Path)
slog.Error("failed", "err", err, "user_id", id)
# Delve — Go debugger
go install github.com/go-delve/delve/cmd/dlv@latest
# Start debugging
dlv debug # Debug current package
dlv debug ./cmd/myapp # Debug specific package
dlv test # Debug tests
dlv attach <pid> # Attach to running process
# Delve commands:
# break main.go:42 (set breakpoint)
# continue (run to breakpoint)
# next (step over)
# step (step into)
# print expr (evaluate expression)
# goroutines (list goroutines)
# stack (show call stack)
# VS Code — built-in Go debugging
# Install Go extension, F5 to debug
# Breakpoints, watch, goroutine inspection
# GoLand (JetBrains) — built-in debugger
# Click gutter, Shift+F9 to debug Variables
Use var for explicit declarations or := for short declarations with type inference.
// Short declaration (most common)
name := "Alice"
age := 30
// Explicit var
var count int = 0
var label string
// Multiple declarations
var (
x int = 1
y string = "hello"
)
// Constants
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
) Types
Statically typed with basic types, structs, slices, maps, and interfaces.
// Basic types
var s string = "hello"
var n int = 42
var f float64 = 3.14
var b bool = true
// Structs
type User struct {
Name string
Age int
Email string
}
// Slices (dynamic arrays)
nums := []int{1, 2, 3}
// Maps
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// Interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
// Type alias
type ID = string Data Structures
Built-in: arrays, slices, maps, and channels. No generics-based collection library in stdlib (use slices/maps packages).
// Arrays — fixed size
var arr [3]int = [3]int{1, 2, 3}
// Slices — dynamic, most common
s := []int{1, 2, 3}
s = append(s, 4)
s[0] // 1
s[1:3] // [2, 3]
len(s) // 4
cap(s) // capacity
// Make with initial capacity
s := make([]int, 0, 100)
// Maps — key-value
m := map[string]int{
"alice": 95,
"bob": 87,
}
m["charlie"] = 72
val, ok := m["alice"] // ok = true
delete(m, "bob")
// Iteration
for i, v := range s { fmt.Println(i, v) }
for k, v := range m { fmt.Println(k, v) }
// Structs as data containers
type Point struct {
X, Y float64
}
// Slices/maps packages (Go 1.21+)
import "slices"
slices.Sort(s)
slices.Contains(s, 3)
idx := slices.Index(s, 2)
import "maps"
maps.Keys(m)
maps.Values(m)
// No built-in set — use map[T]struct{}
set := map[string]struct{}{}
set["a"] = struct{}{}
_, exists := set["a"] // exists = true Functions
Functions can return multiple values. Methods are functions with a receiver.
// Basic function
func greet(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// Variadic function
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Method (function with receiver)
func (u User) FullName() string {
return u.FirstName + " " + u.LastName
}
// Anonymous function
add := func(a, b int) int { return a + b } Conditionals
if/else and switch. Conditions don’t need parentheses.
// If / else (can include init statement)
if x := compute(); x > 0 {
fmt.Println("positive")
} else if x == 0 {
fmt.Println("zero")
} else {
fmt.Println("negative")
}
// Switch (no break needed, implicit)
switch day {
case "Mon", "Tue", "Wed", "Thu", "Fri":
fmt.Println("weekday")
case "Sat", "Sun":
fmt.Println("weekend")
default:
fmt.Println("unknown")
}
// Type switch
switch v := val.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Println("other")
} Loops
Go has only for — it covers all loop patterns.
// Classic for loop
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// While-style
n := 0
for n < 3 {
n++
}
// Infinite loop
for {
break // exit
}
// Range over slice
for i, val := range []string{"a", "b", "c"} {
fmt.Println(i, val)
}
// Range over map
for key, val := range myMap {
fmt.Println(key, val)
}
// Range over string (runes)
for i, ch := range "hello" {
fmt.Printf("%d: %c\n", i, ch)
} Generics & Type System
Statically typed with generics added in Go 1.18. Type parameters use constraints (interfaces).
// Generic function
func Map[T any, U any](s []T, fn func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = fn(v)
}
return result
}
// Usage
doubled := Map([]int{1, 2, 3}, func(x int) int { return x * 2 })
// Constraints — restrict type parameters
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// Built-in constraints
import "cmp"
func Max[T cmp.Ordered](a, b T) T {
if a > b { return a }
return b
}
// Generic types
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
// Comparable constraint
func Contains[T comparable](s []T, target T) bool {
for _, v := range s {
if v == target { return true }
}
return false
} Inheritance & Composition
Go has no classes or inheritance. Uses struct embedding for composition and interfaces for polymorphism. “Composition over inheritance” is idiomatic.
// Struct embedding (composition)
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct {
Animal // embedded — Dog "inherits" Speak()
Breed string
}
// Override by defining same method
func (d Dog) Speak() string {
return d.Name + " barks"
}
// Interfaces (implicit satisfaction)
type Speaker interface {
Speak() string
}
type Writer interface {
Write(data []byte) (int, error)
}
// Compose interfaces
type ReadWriter interface {
Reader
Writer
}
// Any type with Speak() satisfies Speaker
func announce(s Speaker) {
fmt.Println(s.Speak())
}
// Embedding interfaces in structs
type Logger struct {
Writer // embed interface
}
// Empty interface (any type)
func process(v any) { /* ... */ } Functional Patterns
Functions are first-class. Closures and higher-order functions are supported, but Go favours explicit loops over functional chaining.
// First-class functions
func apply(fn func(int) int, x int) int {
return fn(x)
}
double := func(x int) int { return x * 2 }
apply(double, 5) // 10
// Closures
func counter(start int) func() int {
count := start
return func() int {
count++
return count
}
}
inc := counter(0)
inc() // 1
inc() // 2
// Higher-order filter (manual, no generics before 1.18)
func filter[T any](s []T, fn func(T) bool) []T {
var result []T
for _, v := range s {
if fn(v) {
result = append(result, v)
}
}
return result
}
// Map with generics (Go 1.18+)
func mapSlice[T, U any](s []T, fn func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = fn(v)
}
return result
}
evens := filter([]int{1,2,3,4}, func(x int) bool {
return x%2 == 0
}) Concurrency
Goroutines and channels — Go’s concurrency model is built into the language. Lightweight green threads with CSP-style communication.
// Goroutines — lightweight concurrent functions
go func() {
fmt.Println("running concurrently")
}()
// Channels — communicate between goroutines
ch := make(chan string)
go func() {
ch <- "hello" // Send
}()
msg := <-ch // Receive
// Buffered channels
ch := make(chan int, 10)
// Select — multiplex channels
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}
// sync.WaitGroup — wait for goroutines
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
fetch(u)
}(url)
}
wg.Wait()
// sync.Mutex — protect shared state
var mu sync.Mutex
mu.Lock()
counter++
mu.Unlock()
// errgroup — goroutines with error handling
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return fetchA(ctx) })
g.Go(func() error { return fetchB(ctx) })
err := g.Wait() Modules & Imports
Go uses packages and modules. Each directory is a package. A go.mod file defines a module. Imports use the full module path. No circular imports allowed.
// Import standard library
import "fmt"
import "os"
// Multiple imports
import (
"fmt"
"strings"
"net/http"
)
// Import third-party packages
import "github.com/gorilla/mux"
// Import with alias
import (
f "fmt"
_ "image/png" // side-effect import
)
// Package declaration (every file starts with this)
package main // or package mylib
// Exported vs unexported (capitalization)
func PublicFunc() {} // exported (uppercase)
func privateFunc() {} // unexported (lowercase)
// Internal packages (restricted visibility)
// internal/helper.go — only parent module can import
// go.mod defines the module
// module github.com/user/myapp
// go 1.22
// require github.com/gorilla/mux v1.8.1 Error Handling
Errors are values returned from functions. No exceptions — check errors explicitly.
// Return and check errors
result, err := strconv.Atoi("42")
if err != nil {
log.Fatal(err)
}
// Custom errors
type AppError struct {
Code int
Message string
}
func (e *AppError) Error() string {
return fmt.Sprintf("%d: %s", e.Code, e.Message)
}
// Wrapping errors
if err != nil {
return fmt.Errorf("failed to parse: %w", err)
}
// Checking wrapped errors
if errors.Is(err, os.ErrNotExist) {
fmt.Println("file not found")
}
// Defer for cleanup
f, err := os.Open("file.txt")
if err != nil {
return err
}
defer f.Close() Memory Management
Garbage collected with a concurrent, tri-color mark-and-sweep collector. Low-latency GC pauses (typically < 1ms).
// Memory is automatically managed
obj := &MyStruct{Name: "Alice"}
// No need to free — GC handles it
// Stack vs heap — compiler decides (escape analysis)
func createLocal() int {
x := 42 // Stays on stack (doesn't escape)
return x
}
func createHeap() *int {
x := 42 // Escapes to heap (returned as pointer)
return &x
}
// See escape analysis decisions
// go build -gcflags='-m' .
// sync.Pool — reuse temporary objects (reduce GC pressure)
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
// ... use buf ...
bufPool.Put(buf) // Return to pool
// runtime — GC control
import "runtime"
runtime.GC() // Force GC
runtime.ReadMemStats(&stats) // Memory statistics
// GOGC — tune GC aggressiveness
// GOGC=100 (default): GC when heap doubles
// GOGC=50: GC more often (less memory, more CPU)
// GOMEMLIMIT=1GiB: soft memory limit (Go 1.19+)
// Finalizers (rarely needed)
runtime.SetFinalizer(obj, func(o *MyType) {
o.Close()
}) Performance Profiling
Built-in profiling via pprof and the testing package. Go has excellent profiling tools out of the box.
// Benchmarks in test files
func BenchmarkSort(b *testing.B) {
data := generateData(1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sort.Ints(data)
}
}
// Memory benchmarks
func BenchmarkAlloc(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = make([]int, 1000)
}
}
# CPU profiling
go test -bench . -cpuprofile cpu.prof
go tool pprof cpu.prof
# (pprof) top10
# (pprof) web # Opens flame graph in browser
# Memory profiling
go test -bench . -memprofile mem.prof
go tool pprof -alloc_space mem.prof
# HTTP pprof — profile running servers
import _ "net/http/pprof"
# Visit http://localhost:6060/debug/pprof/
# Trace — goroutine scheduling, GC, syscalls
go test -trace trace.out
go tool trace trace.out
# Flame graphs
go tool pprof -http=:8080 cpu.prof
# Benchstat — compare benchmark results
go install golang.org/x/perf/cmd/benchstat@latest
benchstat old.txt new.txt Interop
cgo enables calling C code from Go. Go can also be compiled as a C-shared library. Plugin system for dynamic loading.
// cgo — call C from Go
/*
#include <math.h>
*/
import "C"
import "fmt"
func main() {
result := C.sqrt(16.0)
fmt.Println(result) // 4
}
// Pass strings between Go and C
/*
#include <stdlib.h>
#include <string.h>
*/
import "C"
import "unsafe"
cs := C.CString("hello")
defer C.free(unsafe.Pointer(cs))
length := C.strlen(cs)
// Build Go as a C shared library
// export Add
func Add(a, b C.int) C.int {
return a + b
}
// Build: go build -buildmode=c-shared -o libmylib.so
// os/exec — call system commands
import "os/exec"
out, err := exec.Command("ls", "-la").Output()
// Plugin system (Linux/macOS only)
import "plugin"
p, _ := plugin.Open("myplugin.so")
sym, _ := p.Lookup("Hello")
sym.(func())()
// WASM — compile Go to WebAssembly
// GOOS=js GOARCH=wasm go build -o main.wasm
// gRPC — cross-language RPC
// Go is a primary language for gRPC
// protoc --go_out=. --go-grpc_out=. service.proto Packaging & Distribution
Go produces single static binaries — no runtime needed. Publish modules to any Git host. Cross-compile trivially.
# Publish a Go module — just push to a Git repo
# Users: go get github.com/user/mylib@v1.0.0
# Semantic versioning via Git tags
git tag v1.0.0
git push origin v1.0.0
# Major version paths (v2+)
# module github.com/user/mylib/v2
# Build for multiple platforms
GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o myapp-windows.exe
# Strip debug info for smaller binaries
go build -ldflags="-s -w" -o myapp
# GoReleaser — automated release pipeline
# goreleaser.yaml config, then:
goreleaser release --clean
# Builds for all platforms, creates GitHub release,
# generates checksums, Homebrew formula, Docker images
# Install via go install
go install github.com/user/mycli@latest
# Binary placed in $GOPATH/bin
# Docker (tiny images with scratch)
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /app/myapp
FROM scratch
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
# Final image: ~5-10MB