Go Coding Standards
Idiomatic Go conventions grounded in Effective Go, Go Code Review Comments, and production-proven idioms. All code MUST pass goimports, golint, and go vet without errors.
1. Import Ordering
Group imports in this order, separated by blank lines:
import (
// 1. Standard library
"context"
"fmt"
"net/http"
// 2. External packages
"github.com/gorilla/mux"
"log/slog"
// 3. Internal/project packages
"github.com/myorg/myproject/internal/service"
)NEVER use dot imports. Use aliasing only to resolve conflicts.
2. Naming Conventions
Packages
- Short, lowercase, single-word names. No underscores, no camelCase.
- Name should describe what the package *provides*, not what it *contains*.
- Avoid generic names:
util,common,helpers,misc,base.
Functions & Methods
- MixedCaps (exported) or mixedCaps (unexported). No underscores except in test files.
- Getters: use
Name(), NOTGetName(). Setters: useSetName(). - Constructors:
NewFoo()returns*Foo. If only one type in package:New().
Variables
- Short names in tight scopes:
i,n,err,ctx. - Descriptive names for wider scopes:
userCount,retryTimeout. - Prefix unexported package-level globals with
_:var _defaultTimeout = 5 * time.Second. - Do NOT shadow built-in identifiers (
error,len,cap,new,make,close).
Interfaces
- Single-method interfaces: method name +
-ersuffix (Reader,Writer,Closer). - Define interfaces where they are *consumed*, not where they are *implemented*.
3. Variable Declarations
Top-level
Use var for top-level declarations. Do NOT specify type when it matches the expression:
// ✅ Good
var _defaultPort = 8080
var _logger = slog.Default()
// ❌ Bad — redundant type
var _defaultPort int = 8080Local
- Prefer
:=for local variables. - Use
varonly when zero-value initialization is intentional and meaningful.
// ✅ Good — zero value is meaningful
var buf bytes.Buffer
// ✅ Good — short declaration
name := getUserName()4. Struct Initialization
ALWAYS use field names. Never rely on positional initialization:
// ✅ Good
user := User{
Name: "Alice",
Email: "alice@example.com",
Age: 30,
}
// ❌ Bad — positional, breaks on field reordering
user := User{"Alice", "alice@example.com", 30}Omit zero-value fields unless clarity requires them:
// ✅ Good — zero values omitted
user := User{
Name: "Alice",
}5. Reduce Nesting
Handle errors and special cases first with early returns. Reduce indentation levels:
// ✅ Good — early return
func process(data []Item) error {
for _, v := range data {
if !v.IsValid() {
log.Printf("invalid item: %v", v)
continue
}
if err := v.Process(); err != nil {
return err
}
v.Send()
}
return nil
}Eliminate unnecessary else blocks:
// ✅ Good
a := 10
if condition {
a = 20
}
// ❌ Bad
var a int
if condition {
a = 20
} else {
a = 10
}6. Grouping and Ordering
Group related declarations:
const (
_defaultPort = 8080
_defaultTimeout = 30 * time.Second
)
var (
_validTypes = map[string]bool{"json": true, "xml": true}
_defaultUser = User{Name: "guest"}
)Function ordering within a file:
- Constants and variables
New()/ constructor functions- Exported methods (sorted by importance, not alphabetically)
- Unexported methods
- Helper functions
Receiver methods should appear immediately after the type declaration.
7. Line Length
Soft limit of 99 characters. Break long function signatures:
func (s *Store) CreateUser(
ctx context.Context,
name string,
email string,
opts ...CreateOption,
) (*User, error) {8. Defer Usage
Use defer for cleanup. It makes intent clear at the point of acquisition:
mu.Lock()
defer mu.Unlock()
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()9. Enums
Start enums at 1 (or use explicit sentinel) so zero-value signals "unset":
type Status int
const (
StatusUnknown Status = iota
StatusActive
StatusInactive
)10. Use time Package Properly
- Use
time.Durationfor durations, NOT raw integers. - Use
time.Timefor instants. Usetime.Since(start)instead oftime.Now().Sub(start). - External APIs: accept
intorfloat64and convert internally.
// ✅ Good
func poll(interval time.Duration) { ... }
poll(10 * time.Second)
// ❌ Bad
func poll(intervalSecs int) { ... }
poll(10)Verification Checklist
Before considering code complete:
goimportsruns cleango vet./...passesgolangci-lint runpasses (if configured)- No shadowed built-in identifiers
- All imports properly grouped and ordered
- Struct initializations use field names
- No unnecessary nesting or else blocks