Go conventions for idiomatic code

The Go conventions that make code idiomatic: gofmt, MixedCaps naming, exported vs unexported, doc comments, error style, and accept interfaces return structs.

On this page

Idiomatic Go is remarkably uniform because the community agreed on conventions and let tools enforce them. This guide covers the ones that matter most day to day, so your code reads like everyone else’s.

Let gofmt decide formatting

Formatting is not a matter of taste in Go. gofmt (run via go fmt) rewrites your code into the one canonical style: tabs for indentation, aligned struct fields, sorted imports. Configure your editor to run it on save and never argue about braces or spacing again.

Naming: MixedCaps and short names

Go uses MixedCaps or mixedCaps, never snake_case, for multi-word names.

const MaxRetries = 3

type HTTPClient struct {
	baseURL string
	timeout int
}

Prefer short names, especially for short-lived locals: i for a loop index, r for a reader, buf for a buffer. Longer names earn their length only when the scope is large. Keep initialisms all-caps: HTTPClient and userID, not HttpClient or userId.

Exported vs unexported

Visibility is controlled by the first letter, not a keyword. A capitalised identifier is exported (visible to other packages); a lowercase one is private to its package.

// Message is exported: other packages can call it.
func Message(name string) string {
	return "Hello, " + name
}

// normalize is unexported: usable only inside this package.
func normalize(name string) string {
	return name
}

Export the smallest surface that is useful. Everything public is a promise you have to keep compatible.

Doc comments

A doc comment is a regular comment directly above a declaration, and by convention it begins with the name being documented. go doc and pkg.go.dev turn these into documentation automatically.

// Message returns a greeting for name. It never returns an empty string.
func Message(name string) string {
	return "Hello, " + name
}

Error handling style

Return errors as the last value, check them immediately, and add context when you pass them up with %w so callers can still inspect the original.

data, err := os.ReadFile(path)
if err != nil {
	return fmt.Errorf("read config %s: %w", path, err)
}

Do not ignore errors with _ unless you truly mean to, and handle the error where you have the context to do something useful with it.

Accept interfaces, return structs

A widely used guideline: functions should accept interfaces (so callers can pass any implementation) but return concrete types (so callers get the full, discoverable API).

// Accepts any io.Reader; returns a concrete *Scanner.
func NewScanner(r io.Reader) *Scanner {
	return &Scanner{source: r}
}

This keeps your inputs flexible and your outputs honest. Define interfaces where they are consumed, not where the concrete type is defined.

Package naming

Package names are short, lowercase, single words, and describe what they provide, not what they contain: time, http, greeting. Avoid utils or common grab-bags, and avoid stutter — greeting.Message, not greeting.GreetingMessage.

Summary

  • Let gofmt own formatting; enforce it in CI.
  • Use MixedCaps and short names; keep initialisms all-caps.
  • Control visibility with capitalisation, document exported names, and add context to errors with %w.

Put these into practice with a clean Go project structure, and see the full path in the Go developer roadmap.

Search

Search runs entirely in your browser.