Before You Start
Understand what Go is and get a working toolchain before writing a line of code. These two lessons take about an hour and make everything after them easier.
What is Go?
Origin at Google, design philosophy, and what Go is actually for
Go was created at Google in 2007 by Robert Griesemer, Rob Pike and Ken Thompson because large C++ codebases had become painfully slow to compile and hard to reason about. Understanding that origin explains almost every Go design decision you will meet later — including the ones that look like omissions.
Go's origin story, design goals, where it is used in production, and how it compares to C++, Python and Java.
Setting Up Go
Installing the toolchain, your editor, and running your first program
A correct Go install and a configured editor is the difference between a smooth first week and a frustrating one. This lesson covers installation on all three platforms, the go command, and getting gopls working in your editor.
go version and go run working, and confirm your editor shows errors as you type.Installing Go on Windows, macOS and Linux; editor setup; and running your first program with go run.
Language Fundamentals
The core of the language. Six lessons covering the syntax and built-in types you will use in every Go program you ever write. Do not skip ahead — Go's later features assume these cold.
Variables & Types
Declarations, zero values, and Go's static type system
Go is statically typed with no implicit conversions — a design choice that catches a whole class of bugs at compile time. This lesson also covers zero values, which Go uses instead of null for most types and which shape how idiomatic Go structs are designed.
Declaration forms, Go's basic types, zero values, constants, and why Go refuses implicit numeric conversion.
Control Flow
if, for, switch — and the loops Go deliberately does not have
Go has exactly one loop keyword. There is no while, no do-while, and no ternary operator. This lesson shows how for covers every looping case, and how Go's switch is more capable than C's — no fall-through by default, and it can switch on types.
for keyword replaces while/do-while, and switch without break statements.if with initialisers, all four forms of for, switch on values and types, and labelled break/continue.
Functions & defer
Multiple returns, named results, and deferred cleanup
Two Go habits start here. Functions return multiple values — which is why Go returns errors instead of throwing them — and defer schedules cleanup at the point you acquire a resource, so it cannot be forgotten later.
Function syntax, multiple and named return values, variadic parameters, and how defer/panic/recover interact.
Slices & Arrays
The most misunderstood part of Go, explained properly
Slices are where most newcomers get bitten. A slice is a view onto a backing array, so appending can either mutate shared data or silently copy it depending on capacity. Understanding length vs capacity now prevents a category of confusing bugs later.
Fixed arrays vs slices, the slice header, append and growth behaviour, copy, and the aliasing traps.
Maps
Go's hash table: lookup, the comma-ok idiom, and iteration order
Maps are Go's built-in hash table and appear in nearly every real program. Two things matter early: the comma-ok idiom for distinguishing a missing key from a zero value, and the fact that Go deliberately randomises iteration order so you cannot depend on it.
Creating and using maps, the comma-ok lookup idiom, deletion, and why maps are not safe for concurrent writes.
Strings & Runes
Bytes, runes, and UTF-8 — why len() can surprise you
A Go string is a read-only slice of bytes, not characters. len() returns bytes, ranging over a string yields runes, and indexing gives you a single byte. Getting this straight early avoids a classic source of bugs with any non-ASCII text.
Strings as byte slices, runes vs bytes, UTF-8 handling, strings.Builder, and the strings package.
Go's Type System
Go has no classes and no inheritance. Instead it composes behaviour from structs, methods and implicitly-satisfied interfaces. These five lessons cover the model that replaces OOP.
Structs & Methods
Composite types, and value vs pointer receivers
Structs are how Go models data, and methods attach behaviour to them. The decision that trips people up is value vs pointer receivers — it determines whether a method can mutate its receiver, and it is not just a style preference.
Struct declaration and literals, methods, value vs pointer receivers, and struct comparability.
Pointers
References without pointer arithmetic
Go has pointers but deliberately no pointer arithmetic, which removes most of the danger they carry in C. You need them for mutation, for avoiding large copies, and for the pointer receivers you just met.
Declaring and dereferencing pointers, new(), nil pointers, and when passing a pointer is the right call.
Interfaces
Implicit satisfaction — Go's most distinctive idea
Nothing in Go says implements. A type satisfies an interface simply by having the right methods, which means you can define an interface for types you do not own. This is the single biggest departure from Java-style OOP and it changes how you design packages.
Interface declaration and satisfaction, the empty interface, type assertions and switches, and interface internals.
Embedding
Composition in place of inheritance
Go replaces inheritance with embedding: put one type inside another and its methods are promoted to the outer type. It looks like inheritance at the call site but behaves differently in one important way — there is no virtual dispatch to an overridden method.
Struct and interface embedding, method promotion, name shadowing, and why Go has no superclass.
Generics
Type parameters, added in Go 1.18
Go shipped without generics for over a decade, then added them in 1.18 (2022). They are deliberately narrower than C++ templates or Java generics. Knowing when a type parameter genuinely helps — and when an interface was already the right answer — is the useful skill here.
Type parameter syntax, constraints and the comparable/any predeclared constraints, and generic types vs interfaces.
Concurrency — Go's Defining Feature
This is why most teams choose Go. Concurrency is built into the language rather than bolted on through a library, and these five lessons are the heart of the course. Take them slowly.
Goroutines & Channels
Lightweight concurrency and communication
A goroutine costs a few kilobytes, so Go programs routinely run thousands of them. Channels let those goroutines communicate by passing values rather than sharing memory — the idea Go's concurrency philosophy is built on.
Goroutines and the scheduler, channel mechanics, buffering, closing channels, and ranging over a channel.
select & the sync Package
Multiplexing channels, and when a mutex is still right
select waits on several channels at once, which is how timeouts, cancellation and fan-in are built. And despite the “share by communicating” slogan, sync.Mutex and sync.WaitGroup remain the correct tool for plenty of real problems.
select statements, timeouts via time.After, and sync.Mutex, RWMutex, WaitGroup and Once.
The context Package
Cancellation and deadlines across API boundaries
Once you have goroutines doing real work, you need a way to tell them to stop. context is Go's standard answer, and it appears as the first parameter of almost every function in the modern standard library and in most production Go code.
context.Background and WithCancel/WithTimeout/WithValue, propagation, and the conventions around passing ctx.
Concurrency Patterns
Worker pools, fan-in/fan-out, and pipelines
The primitives are simple; assembling them correctly is the skill. This lesson covers the handful of patterns that solve most real concurrency problems, plus the leak and deadlock mistakes that show up when they are assembled wrong.
Worker pools, fan-in and fan-out, pipeline composition, goroutine leaks, and the race detector.
Memory & the Garbage Collector
Stack vs heap, escape analysis, and Go's low-latency GC
Go is garbage collected, but its collector is tuned for low pause times rather than raw throughput. Understanding escape analysis — why some values land on the stack and others on the heap — is what lets you reason about allocation in hot paths.
Stack vs heap allocation, escape analysis, the GC's design goals, and GOGC tuning.
Idiomatic Go & the Standard Library
Go's standard library is unusually complete — a production HTTP server ships in the box. These seven lessons cover the error-handling style and the packages you will actually reach for daily.
Error Handling
Errors as values, wrapping, and errors.Is/As
Go has no exceptions. Errors are ordinary values returned alongside results, checked explicitly. It is more verbose than try/catch and that is the intended trade-off — every failure path is visible in the code. Wrapping with %w and inspecting with errors.Is/As is the modern idiom.
The error interface, sentinel and custom errors, wrapping with fmt.Errorf and %w, and errors.Is/As/Unwrap.
Closures
Functions capturing their environment
Closures power a lot of idiomatic Go — HTTP middleware, functional options, and deferred cleanup all rest on them. The classic trap is capturing a loop variable, which Go 1.22 changed the semantics of, so older code and newer code behave differently here.
How closures capture variables, the loop-variable trap and its 1.22 fix, and closures as function factories.
Iterators & range
Ranging over collections — and over functions, since Go 1.23
range has worked over slices, maps and channels since day one. Go 1.23 (2024) extended it to range-over-function iterators, which is the biggest change to how Go code traverses custom collections in years.
Ranging over each built-in type, the iter package, and writing range-compatible iterator functions.
The Standard Library Tour
What ships in the box, and what you will use most
Go's library is deliberately broad, which is why Go projects tend to have short dependency lists. This lesson is a map of the packages worth knowing — fmt, strings, time, os, sort, encoding — so you reach for the standard library before an external module.
A guided tour of the most-used standard library packages and what each is for.
JSON & Encoding
Marshalling, struct tags, and encoding/json
Almost every Go service speaks JSON. Struct tags control the mapping between Go field names and JSON keys, and understanding how the encoder treats unexported fields, zero values and omitempty saves a lot of debugging.
Marshal and Unmarshal, struct tags, custom marshallers, streaming with Encoder/Decoder, and other encodings.
I/O & Files
io.Reader, io.Writer, and the interfaces everything composes through
io.Reader and io.Writer are two of the smallest and most important interfaces in Go. Once you see how much of the standard library is built to accept them, composing file, network and in-memory streams becomes routine.
Reading and writing files, io.Reader/Writer, bufio, io.Copy, and the os and path/filepath packages.
HTTP Servers
Production web services with net/http
This is Go's flagship use case. net/http in the standard library is genuinely production-grade — no framework required — and Go 1.22 added method and wildcard routing to the default mux, which removed one of the main reasons people used to reach for a third-party router.
Handlers and HandlerFunc, ServeMux and 1.22 routing patterns, middleware, graceful shutdown, and clients.
Tooling & Shipping
Go's tooling is part of the language's appeal: one command formats, tests, builds and cross-compiles. Five lessons on organising a real project and shipping it.
Packages & Modules
Organising code, and how exports actually work
Go's visibility rule is unusually simple: a capitalised identifier is exported, a lowercase one is package-private. There are no public/private keywords. This lesson covers package layout and the import mechanics that follow from it.
Package declaration and naming, exported vs unexported identifiers, imports, and internal packages.
Modules & Dependencies
go.mod, versioning, and the module proxy
Modules replaced GOPATH in 2019 and are now the only sane way to manage Go dependencies. go.mod and go.sum give you reproducible builds, and Go's minimal version selection resolves dependencies differently from npm or pip in a way worth understanding.
Initialising modules, adding and upgrading dependencies, go.sum verification, vendoring, and the proxy.
Testing
The built-in testing package, table tests, and benchmarks
Testing ships with Go — no framework, no assertion library by default. The table-driven test is the dominant idiom, and go test also runs benchmarks, fuzz tests and the race detector from the same command.
Writing tests, table-driven style, subtests, benchmarks, fuzzing, coverage, and testable examples.
Building & Cross-Compiling
Single static binaries, and GOOS/GOARCH
Go compiles to one static binary with no runtime to install on the target machine — a large part of why Go and containers fit together so well. Cross-compiling for another OS is usually two environment variables.
go build and go install, build tags, cross-compilation, linker flags, and reducing binary size.
Reflection
Inspecting types at runtime — and why to avoid it
The reflect package lets you examine and manipulate types at runtime. It powers encoding/json and most ORMs, but it costs compile-time safety and speed. Worth understanding so you can read library code — and worth avoiding in your own.
reflect.Type and reflect.Value, struct tag inspection, the performance cost, and safer alternatives.
Concepts That Cross Languages
These pages explain ideas that are not specific to Go — they apply to every language you will ever learn. Read them once and the next language costs you far less effort. They pair well with the lessons above rather than replacing them.
Build Real Things — 10 Projects
Reading is not enough. These 10 projects — five basic, five medium — are chosen because each one exercises something specific you learned above, and because they are genuinely idiomatic Go rather than generic exercises. Every one is built, compiled, tested and verified with the real Go toolchain — not just written down. Each link below opens the full walkthrough: the code, the tests, and a plain-English explanation of every part.
Loops, stdin input, and the math/rand package.
Functions, numeric types, and formatted output.
bufio.Scanner, strings.Fields, and the byte-vs-rune gotcha.
Slices, encoding/json, and persisting state to a file.
crypto/rand vs math/rand, and building a string from secure choices.
net/http requests, defer for cleanup, and regexp-based extraction.
net/http's 1.22+ method-routing, JSON encoding, and sync.Mutex. Go's flagship use case.
flag.NewFlagSet subcommands, file persistence, and error handling.
Maps, the comma-ok idiom, and generating unique keys with crypto/rand.
bufio line-by-line reading and a hand-built Counter.most_common with sort.Slice.
Practice & Experimentation
Ongoing, not a final step. Use these throughout the course — try every snippet you read, and run anything you are unsure about rather than assuming.
Run Go code without installing anything, via the official Go Playground.
Copy-ready idiomatic patterns to keep beside you while you build.
All 30 topic pages in one index, for looking things up later.