🐹

A language tour

The Clarity of Go

No magic. No cleverness for its own sake. Just honest, readable code that scales from a weekend project to a cloud platform.

scroll

01 — Simplicity

Less language, more clarity

Go has 25 keywords. The entire spec fits in an afternoon. That's not a limitation — it's a deliberate design choice that makes every Go codebase feel immediately familiar, even when written by a stranger.

hello.go
package main

import "fmt"

func main() {
    // The whole language fits in your head.
    name := "world"        // short variable declaration
    fmt.Printf("Hello, %s!\n", name)

    // Only one kind of loop — and it does everything
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }

    // Range — clean iteration over slices, maps, channels
    words := []string{"simple", "readable", "fast"}
    for _, w := range words {
        fmt.Println(w)
    }
}

One loop construct (for), one way to declare variables short (:=), one way to format code (gofmt). Consistency by design.


02 — Concurrency

Goroutines — lightweight threads for free

Go was built from the ground up to be concurrent. Goroutines cost a few kilobytes of stack space. You can spawn a million of them. Channels let them talk without shared mutable state — no mutexes required for the common case.

"Do not communicate by sharing memory; instead, share memory by communicating."

— The Go Proverbs, Rob Pike
goroutines.go
package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("worker %d processing job %d\n", id, j)
        time.Sleep(time.Millisecond * 100)
        results <- j * 2
    }
}

func main() {
    jobs    := make(chan int, 5)
    results := make(chan int, 5)

    // Spin up 3 concurrent workers — each is a goroutine
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Send 5 jobs, close when done
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Collect all results
    for r := 0; r < 5; r++ {
        fmt.Println(<-results)
    }
}

go worker(...) launches a goroutine with two characters. The channel arrows (<-) make data flow visually obvious in the code itself.


03 — Error Handling

Errors are values, not exceptions

Go treats errors as ordinary return values. There's no hidden control flow, no invisible exception hierarchy. Every failure path is visible at the call site — which means you always know exactly what can go wrong and where.

errors.go
package main

import (
    "errors"
    "fmt"
)

// A custom sentinel error — errors are just values
var ErrInsufficientFunds = errors.New("insufficient funds")

func withdraw(balance, amount float64) (float64, error) {
    if amount > balance {
        return 0, fmt.Errorf("withdraw %.2f: %w", amount, ErrInsufficientFunds)
    }
    return balance - amount, nil
}

func main() {
    balance, err := withdraw(100.0, 150.0)
    if err != nil {
        // Unwrap to check the underlying cause
        if errors.Is(err, ErrInsufficientFunds) {
            fmt.Println("Please top up your account.")
        }
        fmt.Println(err)  // withdraw 150.00: insufficient funds
        return
    }
    fmt.Printf("New balance: %.2f\n", balance)
}

%w wraps an error so callers can inspect the chain with errors.Is — context accumulates without losing the root cause.


04 — Interfaces

Implicit satisfaction, explicit contracts

Go interfaces are satisfied implicitly. You don't declare that a type implements an interface — it just does, the moment it has the right methods. This keeps dependencies loose and makes testing effortless.

interfaces.go
package main

import (
    "fmt"
    "math"
)

// The contract — any shape that can report its area
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ Radius float64 }
type Rect   struct{ W, H float64 }

// Circle satisfies Shape — no "implements" keyword needed
func (c Circle) Area()      float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }

func (r Rect) Area()      float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2*(r.W + r.H) }

func printStats(s Shape) {
    fmt.Printf("Area: %.2f  Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rect{W: 4, H: 6},
    }
    for _, s := range shapes {
        printStats(s)
    }
}

Implicit interface satisfaction means you can retrofit an interface onto a type from an external package — no modification, no inheritance, no ceremony.


05 — Defer

Cleanup that never forgets

defer schedules a function call to run when the surrounding function returns — no matter how it returns. Open a file, defer its close. Acquire a lock, defer its release. Resource management becomes local and obvious.

defer.go
package main

import (
    "fmt"
    "os"
    "sync"
)

func processFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()  // always runs — even on early return or panic

    // ... process the file safely ...
    return nil
}

// Defer with a mutex — the pattern is idiomatic Go
var (
    mu    sync.Mutex
    cache = make(map[string]int)
)

func increment(key string) {
    mu.Lock()
    defer mu.Unlock()  // the unlock lives next to the lock
    cache[key]++
}

func main() {
    // Defers stack — LIFO order
    defer fmt.Println("third")
    defer fmt.Println("second")
    defer fmt.Println("first")
    // prints: first, second, third
}

defer pairs acquisition and release at the same indentation level. You can't forget to unlock — the lock and its defer live side by side.


06 — The Whole Picture

More reasons to reach for Go

Compiles Fast

A million-line program compiles in seconds. Go was designed at Google for large codebases — build speed is a first-class feature.

📦

Single Binary

Go compiles to a self-contained executable. No runtime to install, no dependency hell. Ship a file, run a service.

🔒

Memory Safe by Default

Garbage collected, bounds-checked slices, and no pointer arithmetic in everyday code. Safe without the ceremony of Rust.

🧪

Testing Built In

go test ./... — no test framework needed. Table-driven tests are an idiom, not a library. Coverage reports come free.

🎯

gofmt

One canonical style enforced by a tool. No debates about tabs vs spaces, no linter config. Every Go file looks the same.

🌐

Built the Cloud

Docker, Kubernetes, Terraform, CockroachDB — Go didn't just find its niche, it built the infrastructure the modern internet runs on.