# Go Idiom

> Enforces idiomatic Go style — name length scaled to scope, no stutter, useful zero values, guard clauses, correct defer placement, composition over inheritance, and doc comments in the required form. Use when writing, reviewing, or refactoring any Go code, and when the user asks whether something is idiomatic, mentions gofmt, go vet, golangci-lint, package naming, receiver names, struct embedding, or asks "is this Go-ish", "does this read like Go", "clean up this Go".

- Skill: `caslubbers/go-idiom` (Agent Skill)
- Install (CLI): `npx skillmds@latest add caslubbers/go-idiom`
- Raw SKILL.md: https://api.skillmd.com/api/skills/caslubbers/go-idiom/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: CasLubbers (https://skillmd.com/u/caslubbers)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/caslubbers/go-idiom

---


# Idiomatic Go

## Names scale with scope

The greater the distance between declaration and use, the longer the name. Short names inside short scopes are correct Go, not laziness.

```go
// Good — tight scope, short names
for i, r := range records {
    if r.Total > max { max = r.Total }
}

// Good — package-level, long enough to stand alone
const defaultDialTimeout = 30 * time.Second

// Bad — ceremony inside a two-line loop
for recordIndex, currentRecord := range records { ... }
```

Receivers get one or two letters, consistent across every method on the type: `func (s *Server) Start()`, never `func (this *Server)` or `func (server *Server)`.

## No stutter

The package name is part of every identifier a caller reads. Do not repeat it.

```go
// Bad — callers write http.HTTPServer, bytes.BytesBuffer
package http
type HTTPServer struct{}

// Good — callers write http.Server, bytes.Buffer
package http
type Server struct{}
```

Same rule for functions: `user.NewUser()` should be `user.New()`. Package names are short, lowercase, single words, no underscores, no plurals: `store`, not `stores` or `store_utils`.

## Make the zero value useful

A struct should be usable without a constructor wherever possible.

```go
// Good — var buf bytes.Buffer works immediately
var mu sync.Mutex
var buf bytes.Buffer

// Good — zero value is a ready cache
type Cache struct {
    mu sync.Mutex
    m  map[string][]byte // lazily initialised on first write
}
```

Reach for `New…` only when construction has real requirements. If `New` only sets fields the caller could set, delete it.

## Guard clauses, not nesting

Handle the error and return. The happy path stays at minimum indentation, flush left.

```go
// Good
func load(path string) (*Config, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("open config: %w", err)
    }
    defer f.Close()

    var c Config
    if err := json.NewDecoder(f).Decode(&c); err != nil {
        return nil, fmt.Errorf("decode config: %w", err)
    }
    return &c, nil
}
```

If you find yourself writing `else` after a block that returns, delete the `else`.

## defer goes next to the acquisition

Place `defer` on the line after the resource is acquired and the error is checked — never before the check, never at the end of the function.

```go
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
```

`defer` runs at *function* exit, not block exit. Inside a loop, either extract the body into a function or close explicitly.

## Composition, not inheritance

Go has embedding, not subclassing. Embed to reuse behaviour; embed an interface to satisfy part of it.

```go
type Handler struct {
    *log.Logger // Handler gets Printf, Println, …
    store Store
}
```

Prefer a plain field over embedding when you do not want the inner type's methods on your public surface.

## Files read top-down

Put the exported entry points first and the helpers they call below, in call order. A reader opening
the file meets the package's purpose before its plumbing, and descends one level of abstraction at a
time.

```go
// Good — the story first
func Export(orders []Order) string { ... }
func serialiseRow(o Order) string  { ... }
func escapeQuotes(s string) string { ... }
```

Types go above the methods on them, constructors directly under their type. A function mixing
orchestration with byte-level detail belongs at two levels at once and cannot be placed — split it.

## Accept the standard shapes

- Return `(T, error)`, error last, always checked.
- Take `ctx context.Context` as the first parameter of anything that blocks, does I/O, or spawns work.
- Implement `String() string` for types that get logged; `fmt.Stringer` is free readability.
- Use `any` over `interface{}`, and reach for generics only when the alternative is copy-pasting the same function per type.

## Doc comments

Comments on exported identifiers start with the identifier's name and form a full sentence.

```go
// Store persists orders. A Store is safe for concurrent use.
type Store struct{ ... }

// Get returns the order with the given ID, or ErrNotFound if none exists.
func (s *Store) Get(ctx context.Context, id string) (*Order, error)
```

Comment on *why*, never on what the line already says. Delete commented-out code — the version history has it.

## Duplication is cheaper than the wrong abstraction

A little copying is better than a little dependency. Two similar functions in different packages are fine; do not couple them to share six lines.

## Non-negotiables

`gofmt` decides formatting — never argue with it, never hand-align. Run `go vet` and `golangci-lint` before review. Handle every error or explicitly discard with `_` and a reason.

