Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
fmt is the package you'll touch in literally every Go file you write — for printing, formatting, and crucially, for fmt.Errorf("%w", err) which is how Go wraps errors. The format verbs (%v, %+v, %#v, %w, %T) are not optional knowledge — they are the printf-style language you use for log lines, error messages, and quick debugging. Spending 10 minutes learning the verbs will save you 10 hours over your career.
Go's fmt package uses format verbs — single-character sequences after % — to describe how to render a value. %v is the safe default for any type; %+v adds struct field names; %#v gives Go-syntax output you could paste back into source. %T prints the type, invaluable for debugging interfaces. The verb that sits apart from the rest is %w: it's not for display, it's for error wrapping — fmt.Errorf("db: %w", err) creates a new error that wraps the original so errors.Is and errors.As can unwrap the chain. Every Go codebase uses this pattern heavily; mastering the verbs now prevents an entire class of silent-discard bugs later.
%v, %+v, and %#v. Memorize when each is useful (+v for logs, #v for tests).%w to wrap an error and then errors.Is/errors.As to recover it — this is the canonical Go error pattern.fmt.Sprintf to capture a string instead of printing — useful for log fields and test failure messages.go doc fmt end to end. It's ~200 lines and is one of the highest-leverage things you can read in your first week of Go.Use these three in order. Each builds on the one before.
In one paragraph, explain the relationship between `fmt.Println`, `fmt.Printf`, `fmt.Sprintf`, and `fmt.Fprintf`. When do I reach for each?
What does the `%w` verb actually do under the hood? Walk me through how `errors.Is` and `errors.As` traverse a chain of wrapped errors.
For high-volume structured logging, why do experienced Go teams move away from `fmt.Sprintf` toward `log/slog` (or zap/zerolog)? Cover allocations and the cost of interface-{} boxing.
package main
import "fmt"
type Point struct{ X, Y int }
func main() {
p := Point{3, 4}
fmt.Println(p) // {3 4} — default
fmt.Printf("%v\n", p) // {3 4} — same as Println for structs
fmt.Printf("%+v\n", p) // {X:3 Y:4} — adds field names
fmt.Printf("%#v\n", p) // main.Point{X:3, Y:4} — Go-syntax representation
fmt.Printf("%T\n", p) // main.Point — type only
// %w is THE error-wrapping verb (Go 1.13+):
inner := fmt.Errorf("db unreachable")
wrapped := fmt.Errorf("loading user: %w", inner)
fmt.Println(wrapped) // loading user: db unreachable
fmt.Println(errors.Unwrap(wrapped)) // db unreachable
}go run main.go