Library · Reading notes

Learning Go

By Jon Bodner. Not how to write programs in Go: how to write Go that looks like Go.

FR EN
Learning Go book cover, Jon Bodner

Learning Go

Learning Go: An Idiomatic Approach to Real-World Go Programming

8 /10

« The author wanted to call it 'Boring Go'. In production, boring is a feature. »

  • AuthorJon Bodner
  • Year2024 · O'Reilly Media · 2nd ed.
  • Pages494
  • This page~9 min read
Book rating across 5 dimensionsIdeas7/10Practical9/10Readability9/10Aged well9/10Examples8/10

Modern idiomatic Go, generics included: thinking in Go instead of translating from another language.

Why this book

The preface tells how Bodner wanted to call his book "Boring Go": "properly written, Go is boring". He clarifies right away: "boring does not mean trivial". Go has no inheritance, no exceptions, no function or operator overloading: the list of what's missing is the product. A small, predictable language that doesn't wake you up at 3am.

The trap is that you can write Go that looks like Java or Python. Bodner warns: "you're going to be unhappy with the result and wonder what all the fuss is about". The whole book fits in that promise: learning to write Go that looks like Go. The second edition (January 2024) covers generics, fuzzing, and the first breaking change in the language's history (the Go 1.22 loop variables).

The ideas that stay

1Every declaration tells the reader something

In Go, choosing between var and := isn't a matter of taste. var x int says "I want the zero value, on purpose". x := 10 is the normal form inside a function. Every unused variable is a compile error, every type conversion is explicit: "type conversions are one of the places where Go chooses to add verbosity for clarity" (p. 27). The guaranteed zero value (0, "", nil) kills a whole family of bugs inherited from C. The principle that runs through every page: "write your programs in a way that makes your intentions clear" (p. 17).

2A slice is a tiny struct {pointer, length, capacity}, and every trap follows from it

Chapter 3 hands you THE mental model of Go. A slice is not an array: it's three fields, a pointer to a backing array, a length, a capacity. Everything then explains itself mechanically. append reallocates when capacity is full (hence the mandatory x = append(x, ...)). And above all, slicing a slice copies nothing:

x := []string{"a", "b", "c", "d"}
y := x[:2]            // len 2, cap 4: same backing array
y = append(y, "z")
fmt.Println(x)        // [a b z d] : x changed without being touched

"Be careful when taking a slice of a slice! Both slices share the same memory" (p. 49). The protection exists and few know it: the full slice expression y := x[:2:2], whose third index caps the capacity and forces a fresh array on the next append.

3Everything is passed by copy, even what doesn't look like it

Go is call by value everywhere: every function parameter is a copy. Maps seem to be the exception (changes are visible from the caller), slices half-way (you can modify elements, not the length). Bodner's one-sentence explanation: "Every type in Go is a value type. It's just that sometimes the value is a pointer" (p. 116). Same logic for defer: arguments are evaluated when the defer runs, not when the function exits. Once this model sinks in, no parameter-passing behavior surprises you anymore.

4Pointers are a last resort, and mutability shows in the signature

Where Java and Python hide references everywhere, Go makes mutability visible: a pointer parameter announces "this function may modify your data". Bodner turns it into a rule: prefer returning a new value over mutating through a pointer, because "values make it easier to understand how and when your data is modified" (p. 124). The performance argument is counter-intuitive: data accessed through pointers scattered in RAM is roughly two orders of magnitude slower to read than contiguous values (Forrest Smith's study, cited p. 138). Go's garbage collector is built for latency (pauses under half a millisecond), and the less you allocate on the heap, the less it works.

5Interfaces are implicit, and that's free decoupling

Chapter 7 is the heart of the book. In Go, a type implements an interface without declaring it: having the right methods is enough. Bodner calls it compiler-checked duck typing: the consumer defines the interface it needs, the provider knows nothing about it. Hence the maxim "Accept interfaces, return structs", which Bodner attributes to Jack Lindamood (p. 162). The chapter owns it down to a section title: "Go Isn't Particularly Object-Oriented (and That's Great)" (p. 178). And it defuses THE trap of the language: an interface variable holding a nil pointer is NOT equal to nil, because an interface is nil only if both its type AND its value are.

6Errors are values, and the compiler makes you look at them

"For those used to exceptions, Go's approach feels anachronistic. But solid software engineering principles underlie Go's approach" (p. 203). Bodner's reasoning has two parts: exceptions create invisible execution paths through your code; and since Go requires every declared variable to be read, returning the error forces the caller to handle it, or to ignore it explicitly with _. The chapter's motto: "Idiomatic Go favors code that explicitly outlines the possible failure conditions over shorter code that handles anything while saying nothing" (p. 220). panic exists, but for unrecoverable bugs, not as a disguised exception mechanism.

7Generics, at last, but without zeal

Go waited more than ten years before adding generics, and chapter 8 (new in this edition) explains the trade-off: fast compilation, reasonable binaries, readability. Constraints are interfaces, fully consistent with the rest of the language. And Bodner cools the enthusiasm: "don't feel the need to switch all your code over to using type parameters immediately" (p. 199). His most useful warning: don't replace an interface parameter with a generic one hoping for performance; on a trivial function, the call gets about 30% slower in Go 1.20, because the compiler adds runtime lookups instead of generating one function per type like C++ (p. 200-201).

8Concurrency structures a problem, it doesn't speed it up

Chapter 12 opens with a cold shower: "more concurrency doesn't automatically make things faster, and it can make code harder to understand" (p. 288). Concurrency is not parallelism: it's "a tool to better structure the problem you are trying to solve". The rest of the chapter is a hygiene manual: a goroutine that never exits keeps its memory forever (the goroutine leak, p. 299), the done channel shuts it down cleanly, channels coordinate, mutexes protect a struct field, and the community's motto settles the debate: "share memory by communicating; do not communicate by sharing memory" (p. 313).

Three things I didn't know before reading it

My take, honestly

This is the book I recommend for learning Go, and the reason is simple: it explains why. Most introduction books list syntax; this one justifies every choice the language made, and that's exactly what Go needs, a language whose choices unsettle people (no exceptions, no inheritance, a single loop keyword). Bodner writes clear, short, step by step, and every trap is shown in code that fits in five lines.

The reservations. There's no memorable running example: you remember rules, not stories. The tooling and modules chapters (10 and 11) are useful but will age faster than the rest. And the book assumes you already know how to program: if you've never coded, start elsewhere.

What struck me most is how consistent the book is with the language. Go says: clarity beats cleverness. The book applies that rule on every page, down to advising against its own new features when they add nothing (generics "without zeal"). In 2026, when AI generates Go that reads like translated Java, a book that defines in plain words what "idiomatic" means has become a review tool as much as a manual.

Odilon

Still relevant in 2026?

Yes: the second edition (January 2024) covers Go 1.20-1.22, generics, fuzzing and the new loop rules included. It only misses the iterators that landed with Go 1.23 (range over a function). Everything else is today's Go.

Who is it for?

Read it if

  • You come from PHP, Java, Python or JS and want to learn Go without translating from your old language
  • You already write Go but slices, nil interfaces or goroutine leaks have bitten you
  • You review AI-generated Go and want to know what "idiomatic" means, precisely
  • You've done a Go tutorial and want the book that explains the why behind the syntax

Skip it if

  • You've never programmed: it's a second-language book, the basics are assumed
  • You're looking for architecture or microservices: this teaches the language, not the system
  • You want advanced Go (runtime, scheduler, assembly): not the topic

For going further

The Go course on this site practices exactly these foundations, goroutines and channels included. The Designing Data-Intensive Applications notes in this library cover the systems side (what breaks when it's distributed). And the Testing course extends chapter 15.

Comments (0)

Browse the whole library

More book notes coming: one book at a time, the marrow only.