Effective Go
Author: The Go Authors (Go team) | Length: ~33 pages / ~16.5k words | Sections: 17 | Generated: 2026-08-06
Source: go.dev/doc/effective_go. Written for Go's 2009 release and not actively updated; covers the core language only (no generics, modules, or post-2009 libraries).
How to Use This Skill
- Without arguments - load core frameworks for reference (below).
- With a topic - ask about
slices,channels,interfaces,defer, etc.; I find and read the relevant chapter before answering. - With a chapter - ask for
ch09(Data) orch15(Concurrency); I load that chapter file. - Browse - ask "what chapters do you have?" to see the full index.
When you ask about a topic not in Core Frameworks below, I read the relevant chapter file before answering.
Core Frameworks & Mental Models
Share memory by communicating. Go's slogan: "Do not communicate by sharing memory; instead, share memory by communicating." Pass shared values on channels; only one goroutine owns a value at a time, so data races can't occur by design. Prefer this over mutex-guarded variables; reserve mutexes for cases like reference counts. (Ch 15)
gofmt is the single source of truth for formatting. Never hand-align; run gofmt (file) or go fmt (package). Tabs indent; no line-length limit; no parentheses on if/for/switch. If gofmt's output looks wrong, restructure the code - don't work around it. (Ch 3)
Capitalization is visibility. First letter upper-case = exported; lower-case = package-private. This is semantic, not stylistic. (Ch 5)
Name for how it reads at the call site. Package names: lower-case, single word, match the directory base, favor brevity. Don't stutter - bufio.Reader not BufReader; ring.New when one exported type; once.Do(setup) not DoOrWaitUntilDone. Getters drop Get (Owner for field owner); setters use Set (SetOwner). One-method interfaces are method+-er (Reader, Writer); reuse canonical names/signatures (String, not ToString). Use MixedCaps, never underscores. (Ch 5)
Keep the happy path down the page. if/switch take an init statement (if err := f(); err != nil). When a guard body ends in return/break/continue, drop the else - eliminate error cases as they arise. := lets one err flow through a chain of guards in the same scope. (Ch 7)
for is the only loop; switch is a condition ladder. for init; cond; post {} / for cond {} / for {}. range iterates collections and decodes strings to runes; drop the second value for key-only, use _ for value-only. switch {} (no expression) switches on true - the idiomatic if-else-if. No fallthrough; comma-separated cases. Label a loop and break Loop to exit it from inside a switch. Type switch: switch t := x.(type). (Ch 7)
Multiple returns replace in-band errors and out-params. Return (value, error). Named result parameters document the return and enable naked return. (Ch 8)
defer guarantees cleanup. defer f.Close() right after os.Open - never forgets a close on any return path, and keeps release next to acquire. Deferred args evaluate at the defer; calls run LIFO. It's function-scoped, not block-scoped - that's its power. (Ch 8)
new zeros and returns a pointer; make initializes and returns a value. make is for slices/maps/channels only. Design types whose zero value is usable (bytes.Buffer, sync.Mutex) - it composes transitively. Construct with composite literals (&File{fd: fd}; &File{} == new(File)). Prefer slices over arrays. append returns a (possibly reallocated) slice - always reassign (x = append(x, ...)). Map lookups return the zero value for missing keys; use the comma-ok idiom when absence matters. (Ch 9)
Master fmt and avoid the String() recursion trap. %v/%+v/%#v/%T/%q; define String() string (the Stringer interface) to control formatting. A String() that calls Sprintf("%s", m) on the same string-like type recurses forever - convert to string first or use a non-string verb. (Ch 9, 10)
iota generates constant families; init verifies/registers. const ( _ = iota; KB = 1<<(10*iota); MB ... ). init runs after vars and imports - use it for sanity checks and registration. (Ch 10)
Pointer receiver to mutate or satisfy interfaces. Value methods run on values and pointers; pointer methods run only on pointers (the compiler auto-takes the address of addressable values). A *T receiver is how *ByteSlice satisfies io.Writer. (Ch 11)
Interfaces are implicit, small, and capability-shaped. A type satisfies an interface by implementing its methods - no implements. Convert between same-underlying types to reuse method sets (no copy). Type-assert with comma-ok (v, ok := x.(T)); never bare. When a type only implements an interface, export the interface and return it from the constructor. Any type with methods - struct, int, channel, function - can satisfy one; http.HandlerFunc(f) adapts a function into an http.Handler. (Ch 12)
_ makes intent explicit. Discard unwanted returns, do presence-only map checks, silence unused decls temporarily, import for side effects (import _ "pkg"), and enforce conformance at compile time (var _ I = (*T)(nil)). Never use it to discard an error. (Ch 13)
Embedding is composition, not inheritance. Embed a type to promote its methods and satisfy interfaces for free (io.ReadWriter = Reader + Writer; bufio.ReadWriter embeds *Reader/*Writer). The promoted method's receiver is the inner type - no virtual dispatch. Reach in via the type name to refine. (Ch 14)
Channels synchronize; buffered channels limit throughput. Unbuffered = synchronous handshake. make(chan int, N) is a semaphore of capacity N. Gate goroutine creation (or use a fixed pool) to bound resources - don't just limit running count. Channels are first-class - embed a reply channel for parallel demux. select/default gives non-blocking ops (leaky-buffer free lists). Concurrency (structure) ≠ parallelism (speed). (Ch 15)
Return descriptive errors; reserve panic for the unrecoverable. Implement error with a richer struct (PathError{Op, Path, Err}) when callers may inspect cause; type-assert to recover specific failures. recover works only in deferred functions - isolate failing goroutines or convert internal panics to errors at the package boundary. Never expose panics to clients. (Ch 16)
Chapter Index
| # | Title | Key Frameworks |
|---|---|---|
| ch01 | Introduction | Think from a Go perspective, not by translation |
| ch02 | Examples | Standard library as reference implementation |
| ch03 | Formatting | gofmt, tabs, no line limit, no parens |
| ch04 | Commentary | // vs /* */, doc comments precede decl |
| ch05 | Names | Capitalization=visibility, no stutter, Owner not GetOwner, -er interfaces, MixedCaps |
| ch06 | Semicolons | Automatic insertion, brace-on-same-line |
| ch07 | Control structures | if-init, omit else, for/range, switch{}, type switch, labeled break |
| ch08 | Functions | Multiple returns, named results, defer |
| ch09 | Data | new/make, composite literals, slices, append, maps, comma-ok, fmt, Stringer |
| ch10 | Initialization | Constants, iota, runtime var, init |
| ch11 | Methods | Pointer vs value receiver, methods on any named type |
| ch12 | Interfaces | Implicit satisfaction, conversion, type switch/assertion, HandlerFunc |
| ch13 | The blank identifier | Discard returns, side-effect import, compile-time interface check |
| ch14 | Embedding | Interface/struct embedding, method promotion, not subclassing |
| ch15 | Concurrency | Share by communicating, goroutines, channels, semaphore, fixed pool, leaky buffer |
| ch16 | Errors | error interface, PathError, panic/recover, panic-to-error boundary |
| ch17 | A web server | flag, HandlerFunc, ListenAndServe, template.Must |
Topic Index
- append -> ch09
- blank identifier (
_) -> ch07, ch09, ch10, ch13 - buffered channel / semaphore -> ch15
- channel -> ch15
- comma-ok idiom -> ch09, ch12
- composite literal -> ch09
- concurrency vs parallelism -> ch15
- constant / iota -> ch10
- conversion -> ch12
- defer -> ch08, ch16
- doc comment -> ch04
- embedding -> ch14
- error -> ch16
- exported / capitalization -> ch05
- fmt verbs -> ch09
- for / range -> ch07
- function literal / closure -> ch15
- getter -> ch05
- gofmt -> ch03
- goroutine -> ch15
- Handler / HandlerFunc -> ch12, ch17
- if (init, omit else) -> ch07
- init function -> ch10
- interface -> ch12
- make vs new -> ch09
- map -> ch09
- method (pointer/value receiver) -> ch11
- MixedCaps -> ch05
- multiple return values -> ch08
- named results / naked return -> ch08
- panic / recover -> ch16
- package names -> ch05
- rune -> ch07, ch09
- select -> ch15
- semicolons -> ch06
- slice -> ch09
- Stringer -> ch09
- switch (on true / type switch) -> ch07, ch12
- type assertion -> ch12, ch16
- zero value -> ch09, ch10
Supporting Files
- glossary.md - all key terms with definitions
- patterns.md - all techniques and idioms, by situation
- cheatsheet.md - decision rules, quick tables, and tells
Scope & Limits
This skill covers Effective Go only - the core language as of Go's 2009 release. It does not cover generics, modules, or libraries added after 2009. For modern Go features, combine with project-specific tools or current Go docs. For hands-on work in your codebase, use this skill alongside the project's own conventions.