Learning: Go
Become the engineer trusted to own Go on a team: able to design, ship and operate a production Go service, review someone else's Go and name concretely why a design is wrong, and recognise when a design imported from another language is fighting Go rather than using it.
Latest lesson: 0041. Testing Against a Real Database
Success looks like
- Design and ship a production service: config, structured logging, graceful shutdown, health checks, database access.
- Debug a data race and a goroutine leak in code you did not write, using
-raceandpprof. - Review a colleague's PR and say precisely why an interface, a pointer receiver, or a channel is the wrong tool there.
- Predict what a concurrent program does before running it, from the scheduler and memory model rather than from experiment.
- Cut allocations with evidence from
pprofandbenchstat, not guesswork. - Design a package API you can keep backwards-compatible, and know when not to reach for a goroutine.
Constraints
- Assumes no prior Go. Experience in another language shortens the early stages but is not required, and it brings habits that Go will punish quietly, because a wrong instinct here still compiles.
- Needs only the standard toolchain on any supported OS. Nothing through stage 5 requires paid tooling, a cloud account, or a second machine.
- Reps are small programs that fit one sitting. Spacing them across days is the mechanism, not an inconvenience.
- Version-sensitive material dates fast. Claims about the current release are checked against release notes rather than against books.
Out of scope
- Other languages as subjects in their own right. Comparisons appear only where they stop a habit from being carried into Go.
- Third-party frameworks as subjects in their own right: routers, ORMs, dependency-injection containers, CLI toolkits. The standard library covers routing, structured logging and database access directly, so lessons build on it; where a team has already chosen a framework, judging whether it earns its place is a review skill and belongs in stage 6.
- Frontend, WASM, and mobile targets.
- Kubernetes and infrastructure beyond what one Go service needs to run.
- Compiler and runtime internals past the point where they stop predicting program behaviour.
The arc
Seven stages, zero to senior. Not a lesson list: a stage takes several lessons, and the boundaries are soft.
| Stage | Lessons | Covers | Done when |
|---|---|---|---|
| 1. Foundations | 0001 to 0007 | Types, zero values, value vs pointer semantics, slice and map mechanics including aliasing, strings vs runes vs bytes, package basics | Can predict aliasing and copy behaviour without running the code |
| 2. Idiom | 0008 to 0014 | Errors as values, wrapping with %w, errors.Is/As, implicit interface satisfaction, small interfaces, struct embedding, package layout and naming | Writes Go that a reviewer would not describe as "Java in Go syntax" |
| 3. Concurrency | 0015 to 0021 | Goroutines, channels, select, sync primitives, context cancellation, errgroup, the race detector, the memory model, leak patterns | Can find a leak and a race in unfamiliar code and explain the guarantee that was violated |
| 4. Production | 0022 to 0027 | HTTP services, config, slog, graceful shutdown, health checks, database access, generics where they earn their keep | Has structured a service that survives being operated; stage 7 completes it with metrics, tracing, and tests around the handlers and the database, not only around functions |
| 5. Performance and tooling | 0028 to 0033 | Table-driven tests, fuzzing, benchmarks, pprof, escape analysis, allocation reduction, modules and versioning, release builds | Optimises from a profile and proves the win with benchstat |
| 6. Judgment | 0034 to 0037 | API design and compatibility, when not to use a goroutine, review, reading stdlib source for answers | Trusted to make the call and to explain it to someone else |
| 7. Production Observability and Testing | 0038 to 0041 | Middleware and request-scoped values, metrics and tracing (RED/USE, OpenTelemetry), testing an HTTP handler, integration testing against a real database | Ships a service with the third and fourth signal stage 4 stopped short of: metrics, tracing, and tests around the handlers and the database, not only around functions |
Lessons
Work through these in order.
| # | Lesson | Teaches |
|---|---|---|
| 0001 | Values and the Zero Value | Every declared variable is already usable, and nil is a value rather than an absence |
| 0002 | Value Semantics and Pointers | Every assignment and every argument is a copy, and a pointer is how you opt out |
| 0003 | Slices and the Backing Array | A slice is a three-word header over an array someone else may also be holding |
| 0004 | Maps and Their Rules | Comma-ok, unaddressable elements, randomised iteration, and the fatal error a shared map throws |
| 0005 | Strings, Bytes and Runes | A string is immutable UTF-8 bytes, so len is not a character count and indexing is not a character |
| 0006 | Methods and Method Sets | Which receiver you pick decides which types satisfy an interface, not just who can mutate |
| 0007 | Packages and Modules | A directory is a package, a capital letter is the whole visibility system, and internal is enforced |
| 0008 | Errors Are Values | error is an ordinary interface, so failures are data you handle rather than control flow that escapes |
| 0009 | Wrapping, Is and As | %w builds a chain, Is and As search it, and wrapping is an API commitment |
| 0010 | defer, panic and recover | Arguments freeze at the defer statement, and panic is for bugs rather than for failures |
| 0011 | Interfaces Are Satisfied Implicitly | The consumer declares the interface, the implementation never mentions it, and small is the point |
| 0012 | The Nil Interface Trap | An interface holds a type and a value, so a nil pointer inside it is not a nil interface |
| 0013 | Embedding Is Not Inheritance | Promotion looks like subclassing until an embedded method refuses to call your override |
| 0014 | Designing a Package | Doc comments, a small exported surface, and a dependency arrow that points one way |
| 0015 | Goroutines and What They Cost | Cheap enough to start thousands, never free enough to start without a plan to stop |
| 0016 | Channels | Unbuffered means rendezvous, closing is a broadcast, and only the sender may close |
| 0017 | select and Timeouts | Waiting on several channels at once, choosing randomly among the ready, and disabling a case with nil |
| 0018 | context and Cancellation | One value that carries a deadline and a stop signal down every call it touches |
| 0019 | sync and errgroup | When sharing memory beats passing it, and the four primitives that cover almost everything |
| 0020 | The Memory Model and Races | A data race is undefined behaviour, not a coin flip, and the detector only sees what runs |
| 0021 | Goroutine Leaks | A goroutine blocked forever is never collected, and neither is anything it holds |
| 0022 | An HTTP Server Worth Operating | Handlers, routing patterns and the four timeouts the default server does not set |
| 0023 | Configuration and Startup | A main that only wires, a run function that returns an error, and validation before the first request |
| 0024 | Structured Logging with slog | Key-value attributes, one logger passed as a dependency, and levels you can change without a deploy |
| 0025 | Graceful Shutdown and Health | Catch the signal, stop accepting, drain in-flight work, then close dependencies in order |
| 0026 | Talking to a Database | sql.DB is a pool, every call takes a context, and an unclosed Rows holds a connection |
| 0027 | Generics That Earn Their Keep | Type parameters remove duplication across types, and an interface is still the right answer for behaviour |
| 0028 | Table-Driven Tests | One test function, a slice of cases, subtests that name themselves and fail independently |
| 0029 | Fuzzing | State a property that must always hold, then let the toolchain search for the input that breaks it |
| 0030 | Benchmarks You Can Trust | b.Loop, repeated runs and benchstat, because one number from one run is not a measurement |
| 0031 | Reading a pprof Profile | Find where the time and the memory actually go, before changing a single line |
| 0032 | Escape Analysis and Allocation | Why a value lands on the heap, how to ask the compiler, and which fixes actually pay |
| 0033 | Modules and Release Builds | Minimal version selection, the v2 path rule, and the flags that make a binary reproducible |
| 0034 | API Design and Compatibility | What you can add without breaking callers, and the three changes that always do |
| 0035 | When Not to Use a Goroutine | Concurrency is a structure, not a speedup, and the sequential version is often the right answer |
| 0036 | Reviewing Go | Let the tools find style, spend your attention on lifecycle, boundaries and what the compiler cannot check |
| 0037 | Reading the Standard Library | The source is on your machine, it settles arguments the docs cannot, and it is the style reference |
| 0038 | Middleware and Request-Scoped Values | Lesson 22 showed a middleware writing a value into the context; this lesson is the other half, reading it back out safely, and the mechanism that example never needed, a middleware that decides not to call the next handler at all |
| 0039 | Metrics and Tracing | pprof profiles one process's own CPU and memory locally; metrics and tracing are the two production signals that answer a different pair of questions, how the service is doing right now in aggregate, and what happened to this one request across every service it touched |
| 0040 | Testing an HTTP Handler | Lesson 22 mentioned in passing that a ResponseRecorder makes a handler testable with no network; this lesson is that claim made concrete, why the interface seam is what makes it possible, and the different, easily confused tool for testing a client instead of a handler |
| 0041 | Testing Against a Real Database | This is stage 7's capstone, testing.Short() gates a slow database test out of the fast local loop, a transaction begun and never committed gives each test a clean, isolated slate, and one integration test finally exercises middleware, a handler and a real database together |
Reference
- Glossary: canonical terms for this topic
- Resources: trusted sources, each annotated with what it covers
- Slices and Maps: aliasing, capacity, and the operations that panic
- Error Handling: wrapping verbs, matching functions, and the traps that compile
- Concurrency Patterns: channel rules, context constructors, leak checklist
- Toolchain Commands: test, benchmark, profile and release-build flags
- Review Checklist: what the tools catch and what you must
How this works
Each lesson is short and self-contained. Answer keys are collapsed: recall first, then open them. The real-world reps matter more than the reading, and spacing them out is the point. Anything still unclear at the end of a lesson is worth chasing to its primary source before moving on.