20 / 20 · Day 7 · Final
Day 7 · Concept 20 · Final

Idiomatic Go

Go's reputation for simplicity is half real and half discipline. The language IS small. But idiomatic Go has a hundred small conventions — naming, error handling, package layout, interface placement — that take a year to absorb. This page distills the twenty most-frequently-violated ones.


1 · Naming — the short-name rule

Go names get shorter the closer to the use site. Package names are short (http, not http_client). Local variables are very short (i, r, err). Exported types are descriptive (http.Client, os.File).

  • Package names: one word, lowercase, singular. store, not stores or data_store.
  • Receivers: 1-2 chars matching the type. (c *Client), (s *Server). NEVER this or self.
  • Errors: end with "Error" or start with "Err". os.ErrNotExist, type NotFoundError.
  • Constructors: New + type name. NewClient, NewServer. Returns the type (or pointer to it).
  • Don't stutter. http.HTTPServer is wrong; http.Server is right. The package name already says HTTP.

2 · Error handling rituals

  • Check err first, return early. if err != nil { return ... } on the line after every fallible call.
  • Wrap with context. fmt.Errorf("opening config: %w", err) — never lose information.
  • Don't panic in libraries. Return errors. Panic is for "programmer bug" only.
  • Error messages: lowercase, no trailing punctuation. They compose: "opening: not found".

3 · Interfaces — accept, return

"Accept interfaces, return structs." A function should accept the smallest interface it needs, but return concrete types. The caller composes; the callee doesn't make decisions about polymorphism.

4 · Concurrency rules

  • Every goroutine must have a way to exit. Context cancel, channel close, or a finite task.
  • "Start a goroutine when in doubt" is wrong. Start one when you actually need concurrency.
  • Channels for communication; mutexes for shared state. Don't channelify a counter.
  • The first parameter is usually ctx context.Context. Every long-running or blocking function.

5 · Documentation comments

go doc.go · the godoc rules
// Package store provides a key-value store backed by Redis.
package store

// User represents a logged-in person.
//
// Multiple lines are fine. The first sentence (up to first period)
// is the synopsis shown in package listings.
type User struct {
    Name string
    Age  int
}

// Authenticate verifies the user's credentials against the database.
// It returns ErrInvalidCredentials if the password doesn't match.
//
// This is the function-level doc comment. Goes immediately above the
// declaration. Starts with the function name.
func Authenticate(username, password string) (*User, error) {
    // ...
}

6 · The twenty patterns at a glance

#RuleExample
1Use := in functions, var at package scope.x := 5 vs var pi = 3.14
2Return early; don't nest happy-path inside else.if err != nil { return err }
3Group related fields in structs.Name, Email string
4Method receivers are short (1-2 chars).(c *Client)
5Errors wrap with %w; never reformat with %v.fmt.Errorf("foo: %w", err)
6Test files end with _test.go; same package OR _test suffix.store_test.go
7Use defer for cleanup right after acquisition.f.Close() deferred
8Make zero values useful.var b bytes.Buffer works
9One main() per binary; in cmd/<name>.cmd/server/main.go
10No nil on slice/map zero values for ranging.var xs []int; for _, x := range xs { ... }
11Don't expose sync.Mutex; embed and lock inside methods.(c *Cache) Get(k)
12Channels are bidirectional internally; one-way at API boundaries.func gen() <-chan int
13Context is the first parameter.func Fetch(ctx context.Context, url string)
14Constants over magic numbers.const MaxRetries = 3
15Prefer slice over map when keys are sequential ints.[]Item not map[int]Item
16Tests run independently.No shared state across TestXxx
17Use internal/ aggressively.Private until proven public
18Format errors lowercase, no period."not found" not "Not found."
19One package per directory.No multi-package dirs
20Run go vet + golangci-lint in CI.Catches half of beginner bugs

7 · From the wild — read good Go

  • The standard library. The net/http, io, encoding/json, context packages are all under ~3000 LOC each and beautiful examples of idiomatic Go.
  • etcd. The distributed KV store under Kubernetes. The Raft implementation is the cleanest you'll find anywhere.
  • Prometheus. Production monitoring system. Heavy use of interfaces, context, and clean package boundaries.
  • grpc-go. Real RPC at scale. Complex but excellent code organisation.
  • The Go compiler itself. Written in Go. src/cmd/compile. Approachable.

8 · The official references

9 · Exercises (~30 min)

  1. Audit your code. Pick something you've written. Run gofmt -l ., go vet ./..., golangci-lint run. Fix everything.
  2. Refactor a long function. Find one over 80 lines. Break into helpers. Watch readability climb.
  3. Replace a global with a struct. Singletons are a smell. Replace var defaultClient *Client with passing it explicitly.
  4. Read 100 lines of grpc-go. Pick any file. Note one idiom you'd not seen. Copy it for your own use.

10 · You've completed the week

Twenty concepts. From go run hello.go to writing production-grade HTTP services with idiomatic concurrency. You should now be able to:

  • Read any Go program and understand it on first scan.
  • Write a small HTTP service in 30 lines from memory.
  • Spot common bugs (nil maps, slice aliasing, typed-nil errors) in code review.
  • Reach for the right concurrency primitive (channel, mutex, atomic) for the situation.
  • Test idiomatically with table-driven structure.
  • Know where to look in the standard library before reaching for a third-party package.

The week is over; the practice begins. Build something. Ship it. Profile it. Read the standard library. The language gets out of your way; the rest is engineering.

Found this useful?