Go Philosophy Basics
10 examples to get you started with Go Philosophy - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Go Philosophy - 7 basic and 3 intermediate.
go version).main - Clarity FirstGo favors a short entry point that wires dependencies and runs.
package main
import "fmt"
func main() {
fmt.Println("ship small, readable programs")
}package main marks an executable; library code uses other package names.main keeps startup flow easy to audit during incidents.Related: Why Go Exists - the design goals behind this layout
Errors are values returned alongside results, not thrown exceptions.
package main
import (
"fmt"
"os"
)
func readConfig(path string) ([]byte, error) {
return os.ReadFile(path)
}
func main() {
data, err := readConfig("config.yaml")
if err != nil {
fmt.Fprintf(os.Stderr, "read config: %v\n", err)
os.Exit(1)
}
fmt.Printf("loaded %d bytes\n", len(data))
}if err != nil is repetitive by design; it keeps failure paths visible in review.fmt.Errorf("read config: %w", err) preserves causes for logs.Related: Simplicity as a Deliberate Constraint in API Design - why APIs return plain errors
Go defines interfaces where they are consumed, not where types are declared.
package notify
import "context"
type Sender interface {
Send(ctx context.Context, msg string) error
}
func Deliver(ctx context.Context, s Sender, msg string) error {
return s.Send(ctx, msg)
}Concurrency is a first-class language feature, not only a library concern.
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string, 1)
go func() {
time.Sleep(50 * time.Millisecond)
ch <- "done"
}()
fmt.Println(<-ch)
}go starts a function concurrently; the runtime schedules it onto OS threads.Related: Why Go Exists - CSP-inspired concurrency model
defer for Reliable CleanupDefer registers cleanup that runs when the surrounding function returns.
package main
import (
"fmt"
"os"
)
func writeTemp() error {
f, err := os.CreateTemp("", "demo-*.txt")
if err != nil {
return err
}
defer func() {
f.Close()
os.Remove(f.Name())
}()
_, err = f.WriteString("hello")
return err
}
func main() {
if err := writeTemp(); err != nil {
panic(err)
}
fmt.Println("ok")
}defer pairs acquisition with release even when multiple return paths exist.Go ships batteries for HTTP, JSON, TLS, and testing in the toolchain you already have.
package main
import (
"encoding/json"
"net/http"
)
type Health struct {
Status string `json:"status"`
}
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(Health{Status: "ok"})
})
http.ListenAndServe(":8080", nil)
}net/http is production-capable for many services before you reach for Gin, Echo, or chi.go test in CI.Related: Go's Target Domains - cloud-native defaults
Tests document behavior as data rows, matching Go's preference for explicitness.
package mathx
import "testing"
func Add(a, b int) int { return a + b }
func TestAdd(t *testing.T) {
cases := []struct {
a, b, want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tc := range cases {
if got := Add(tc.a, tc.b); got != tc.want {
t.Fatalf("Add(%d,%d)=%d, want %d", tc.a, tc.b, got, tc.want)
}
}
}go test is the universal CI entry point - no extra test runner required.context.Context at API BoundariesCancellation and deadlines propagate through services the Go way.
package fetch
import (
"context"
"io"
"net/http"
"time"
)
func Get(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func GetWithTimeout(url string, d time.Duration) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), d)
defer cancel()
return Get(ctx, url)
}context.Background() only at the outermost main or test harness keeps trees coherent.Related: Go's Target Domains - distributed service boundaries
Public functions depend on narrow interfaces; constructors expose concrete types.
package storage
import "context"
type Store interface {
Put(ctx context.Context, key string, value []byte) error
}
type FileStore struct {
dir string
}
func NewFileStore(dir string) *FileStore {
return &FileStore{dir: dir}
}
func (f *FileStore) Put(ctx context.Context, key string, value []byte) error {
// implementation omitted
return nil
}
func Save(ctx context.Context, s Store, key string, value []byte) error {
return s.Put(ctx, key, value)
}Save mock Store without importing FileStore internals.NewFileStore) document valid initialization in one place.Related: Simplicity as a Deliberate Constraint in API Design - shaping stable package APIs
Philosophy includes knowing where another language wins.
// Go excels: static binary CLI with flags and subcommands.
// Consider Rust: firmware-adjacent performance with no GC.
// Consider Python: exploratory data science with notebook iteration.
// Consider JVM: mature enterprise integrations your team already operates.
package main
import "fmt"
func main() {
fmt.Println("choose Go when team velocity and operability dominate the ADR")
}Related: When Go Is the Wrong Tool - concrete counter-examples | Go vs Rust - systems trade-offs
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026