Home › Programming languages › Managed & backend
guide low

Go

Problem Is Go the right language for a network service, proxy or infrastructure tool? This guide covers what it is good at, and where tail latency or heavy numerics call for something else.

A small, boring language with goroutines, a low-pause GC, fast compiles and single static binaries; the default for network services and infrastructure tools.

gogolanggoroutinesgcbackendcloud-nativestatic-binary
Overview

Go trades expressiveness for simplicity: few features, one formatting style, a standard library covering HTTP, TLS, JSON and testing, and compiles in seconds. Goroutines are cheap user-space threads multiplexed over OS threads, so you write blocking code and the runtime turns it into an event loop. A concurrent GC keeps pauses well under a millisecond, paid in CPU and memory headroom.

Key facts

Use when

  • HTTP/gRPC services, proxies, CLIs, Kubernetes operators, infrastructure tooling; teams valuing readability.
  • Real uses by field (go.dev case studies): payments (PayPal, American Express), CDN and DNS (Cloudflare), databases (CockroachDB), retail banking (Curve), large-scale internet services (Google, ByteDance).

Avoid when

  • Very tight p99.9 targets where GC jitter matters (Rust/C++); SIMD-heavy numerics or ML.

Deep dive

Decision table

JobPickWhy
REST/JSON servicenet/http (1.22+ routing patterns) or chiStdlib is production-grade
gRPCgoogle.golang.org/grpc, or connect-goFirst-class codegen
Databasedatabase/sql + pgx (Postgres), sqlc for typed queriesSimple, fast
Concurrency fan-out with errorsgolang.org/x/sync/errgroupCancellation on first error
CLIcobra or stdlib flagStatic binary distribution
Kubernetes controllerscontroller-runtime / kubebuilderThe K8s ecosystem is Go
Logginglog/slog (stdlib)Structured, no dependency
Hot CPU kernelKeep in Go, or call Rust/C via cgo only if big batchescgo calls cost order of tens of ns and complicate builds

Memory and performance model

  • Escape analysis decides stack vs heap; go build -gcflags=-m shows why something escapes. Fewer heap allocations = less GC work.
  • GC cost is proportional to live heap and allocation rate; GOGC=100 (default) lets the heap grow to about 2x live before collecting. GOMEMLIMIT sets a soft cap so containers don't OOM.
  • Interfaces are two words (type, data); calls through them are indirect and block inlining.
  • Slices are (pointer, len, cap) views; maps are hash tables with randomized iteration.
  • Profiling built in: pprof CPU, heap, goroutine, mutex and block profiles; go tool trace.

Concurrency model

  • M:N scheduler: G (goroutines) on M (OS threads) via P (processors, GOMAXPROCS, cgroup-aware since Go 1.25), with work stealing and preemption.
  • Network I/O parks the goroutine and uses epoll/kqueue under the hood; blocking syscalls hand off the P to another thread.
  • Channels for ownership transfer and pipelines; sync.Mutex, sync/atomic for shared state; context.Context for cancellation and deadlines through call chains.
  • Race detector: go test -race instruments memory accesses; run it in CI.

Code

Service skeleton: routing, timeouts, bounded worker pool, graceful shutdown (Go 1.22+).

package main

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

type Job struct{ ID string }

func worker(ctx context.Context, jobs <-chan Job) {
	for {
		select {
		case <-ctx.Done():
			return
		case j := <-jobs:
			slog.Info("processing", "id", j.ID)
			time.Sleep(50 * time.Millisecond) // real work here
		}
	}
}

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	jobs := make(chan Job, 1024) // bounded queue = backpressure
	for i := 0; i < 8; i++ {
		go worker(ctx, jobs)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("POST /jobs/{id}", func(w http.ResponseWriter, r *http.Request) {
		select {
		case jobs <- Job{ID: r.PathValue("id")}:
			w.WriteHeader(http.StatusAccepted)
		default:
			http.Error(w, "queue full", http.StatusServiceUnavailable) // shed load, don't block
		}
	})
	mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
		_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
	})

	srv := &http.Server{
		Addr:              ":8080",
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second, // defaults are "no timeout": slowloris otherwise
		ReadTimeout:       30 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       120 * time.Second,
		MaxHeaderBytes:    1 << 20,
	}
	go func() {
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			slog.Error("listen", "err", err)
			os.Exit(1)
		}
	}()

	<-ctx.Done()
	shutdownCtx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
	defer cancel()
	_ = srv.Shutdown(shutdownCtx) // stop accepting, finish in-flight requests
}

Build:

CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o app .   # static, reproducible paths
GOMEMLIMIT=900MiB ./app                                      # in a 1 GiB container

Efficiency

  • Cost: a new goroutine gets a few KB of stack, and hundreds of thousands are practical (Go FAQ). GC CPU per cycle is a fixed cost plus a cost per byte of live heap, and more pointers mean more GC work (Go GC guide). A cgo call costs order of tens of ns, far more than a Go call. Bound: allocation rate and live-heap pointers (GC CPU), then memory.
  • Levers:
    • fewer heap allocations (go build -gcflags=-m, escape analysis) — less GC work (GC guide) — hot request paths
    • raise GOGC — doubling it doubles heap overhead and roughly halves GC CPU (GC guide) — when memory is spare
    • GOMEMLIMIT — soft cap that stops container OOMs (GC guide) — containers with a fixed memory limit
    • profile-guided optimization — around 2-14 % on Go 1.22 benchmarks (go.dev/doc/pgo) — CPU-bound services with a representative profile
    • value slices and indices instead of pointer graphs — fewer pointers to scan (GC guide) — large live heaps
  • Naive → standard → state of the art:
    • allocate per request, default GOGC=100 · GC CPU grows with allocation rate
    • escape-analysis fixes, GOGC and GOMEMLIMIT tuned from pprof · GC CPU and memory trade set on purpose
    • the above plus PGO with a production profile · a further 2-14 % CPU

Build and deploy

  • Modules (go.mod, go.sum) with checksum database; go mod tidy, govulncheck ./... for reachable vulnerabilities.
  • Static binary into FROM gcr.io/distroless/static or scratch (add CA certificates).
  • Fast compiles make large monorepos workable; go vet, staticcheck, golangci-lint in CI.
  • PGO: drop a CPU profile as default.pgo in the main package for order of 2-14 % speedups.

Pitfalls

  • Goroutine leaks: goroutines blocked forever on channels nobody reads; always tie them to a context.
  • http.Client{} and http.Server{} without timeouts.
  • A nil pointer stored in an interface is not a nil interface (err != nil is true).
  • Data races on maps: concurrent map writes crash the process; use a mutex or sync.Map for specific patterns.
  • Sub-slices keep the whole backing array alive; append may alias another slice's memory.
  • defer in long loops accumulates until function return.
  • Ignoring errors (_ =) on writes, closes and Rows.Err().
  • Large heaps of pointer-rich data structures make GC expensive; use value slices and indices.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
Go toolchain (gc)Gogolang/gobrew install go (or the installer from go.dev/dl)————compiler, go test -race, pprof, go vet, govulncheck via go install
golangci-lintGogolangci/golangci-lintbrew install golangci-lint————runs many linters in one pass; CI standard
staticcheckGodominikh/go-toolsgo install honnef.co/go/tools/cmd/staticcheck@latest————static analysis beyond go vet
pgxGojackc/pgxgo get github.com/jackc/pgx/v5————Postgres driver and toolkit
grpc-goGogrpc/grpc-gogo get google.golang.org/grpc————gRPC for Go

Sources

  • The Go Authors, The Go Programming Language Specification. spec · The Go Memory Model. spec
  • The Go Authors, A Guide to the Go Garbage Collector. docs
  • Donovan, Kernighan, The Go Programming Language, Addison-Wesley, 2015.
  • The Go Authors, Profile-guided optimization. docs
  • The Go Authors, Go FAQ (goroutines). docs
  • The Go Authors, Go case studies. docs

Alternatives and related