Compare

Lang Compare

Go

Systems

Overview

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