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".
Scanned 9/19/2026
npx -y skills add CasLubbers/code-design-skills --skill go-idiom --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Go Idiom?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/caslubbers-go-idiom)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: go-idiom
description: 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".
---
# 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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!