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
- Go 1.22 made the first breaking change in the language's history (each loop iteration now creates a new variable, killing the classic bug of goroutines all capturing the last value). What makes it possible: the
godirective in go.mod, which pins the behavior per module (preface + ch. 4). - 100% test coverage proves nothing: Bodner demonstrates it live with a buggy function that passes a fully-covered test suite ("code coverage is necessary but not sufficient", p. 386).
- Naming your receiver
thisorselfis explicitly nonidiomatic (p. 144): Go rejects even the vocabulary of classic OO.
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)