Go – Naming Conventions
Go naming conventions are deliberately minimal and opinionated. They differ significantly from Java, Python, and C# — and those differences are intentional design decisions, not gaps to be filled with conventions from other languages. Understanding why Go names things the way it does is as important as knowing the rules.
The Golden Rule
In Go, capitalisation controls visibility. An identifier starting with an uppercase letter is exported (public). One starting with a lowercase letter is unexported (package-private). This is enforced by the compiler — there are no public or private keywords.
Quick Reference
| Identifier | Convention | Example |
|---|---|---|
| Package | lowercase, single word | httputil, orderservice |
| File | snake_case.go |
order_service.go, http_client.go |
| Exported type | MixedCaps |
OrderService, PaymentProcessor |
| Unexported type | mixedCaps |
orderCache, paymentState |
| Exported function | MixedCaps |
NewOrderService(), ParseURL() |
| Unexported function | mixedCaps |
parseResponse(), buildQuery() |
| Exported constant | MixedCaps |
MaxRetryCount, DefaultTimeout |
| Unexported constant | mixedCaps |
defaultPageSize, maxBufferSize |
| Interface (single method) | Method name + er |
Reader, Writer, Stringer |
| Interface (multi method) | MixedCaps noun |
OrderRepository, PaymentGateway |
| Method receiver | Short abbreviation | o for Order, s for Service |
| Local variable | Short, mixedCaps |
n, buf, userID, orderTotal |
| Error variable | err |
err, or ErrNotFound for sentinel |
| Test function | Test<Function>_<Scenario> |
TestCreateOrder_WhenCartEmpty |
| Benchmark function | Benchmark<Name> |
BenchmarkCalculateTotal |
| Example function | Example<Name> |
ExampleOrderService_CreateOrder |
The MixedCaps Rule
Go uses MixedCaps (exported) or mixedCaps (unexported) for all multi-word identifiers without exception. There are no underscores in Go identifier names — not in variables, not in functions, not in types, not in constants.
// ✅ Correct — MixedCaps throughout
type OrderService struct{}
func NewOrderService() *OrderService {}
const MaxRetryCount = 3
var defaultTimeout = 30 * time.Second
// ❌ Incorrect — underscores, snake_case, UPPER_SNAKE
type Order_Service struct{} // underscores not used
const MAX_RETRY_COUNT = 3 // UPPER_SNAKE is not Go convention
var default_timeout = 30 // snake_case is not used in Go
Packages
Package names are the single most important naming decision in Go — they become the prefix every caller uses: http.Get, json.Marshal, fmt.Println.
- All lowercase, no underscores, no
mixedCaps - Single word where possible; concatenate if necessary
- Short, concise, evocative — callers type this constantly
- Do not repeat the package name in exported identifiers (avoid stutter)
// ✅ Correct package names
package httputil
package orderservice
package payment
package config
// ❌ Incorrect
package httpUtil // mixedCaps not used
package order_service // underscore not used
package util // too generic
package common // too generic
package interfaces // never name a package "interfaces"
Avoid Stutter
Because callers always qualify names with the package, do not repeat the package name in the identifier:
// Package: order
// ❌ Stutter — callers write order.OrderService, order.NewOrderService
type OrderService struct{}
func NewOrderService() *OrderService {}
// ✅ No stutter — callers write order.Service, order.New
type Service struct{}
func New(repo Repository, logger *slog.Logger) *Service {}
Interfaces
Single-method interfaces are named with the method name plus the -er suffix:
// ✅ Single-method — method name + er
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type OrderFetcher interface {
FetchOrder(ctx context.Context, id int64) (*Order, error)
}
// ✅ Multi-method — descriptive noun
type OrderRepository interface {
FindByID(ctx context.Context, id int64) (*Order, error)
Save(ctx context.Context, order *Order) error
Delete(ctx context.Context, id int64) error
}
Keep interfaces small
A two-method interface is easier to implement, mock, and satisfy than a ten-method one. If an interface is growing large, break it into smaller, composable pieces.
Functions and Methods
Functions that return something use noun-like names. Functions that do something use verb-like names:
// ✅ Returns something — noun-like
func NewOrderService(repo OrderRepository) *OrderService {}
func ParseURL(raw string) (*url.URL, error) {}
func (o *Order) Total() Money {}
// ✅ Does something — verb-like
func (s *OrderService) ProcessPayment(ctx context.Context, p *Payment) error {}
func (o *Order) Cancel() error {}
Avoid the Get Prefix
In Go, the Get prefix is not used for accessor methods:
// ✅ Go style — no Get prefix
func (o *Order) Status() OrderStatus { return o.status }
func (u *User) Email() string { return u.email }
// ❌ Not Go style
func (o *Order) GetStatus() OrderStatus { return o.status }
Constructors
Go uses the New prefix for constructor functions:
// ✅ Constructor validates and returns ready-to-use value
func NewOrderService(repo OrderRepository, logger *slog.Logger) (*OrderService, error) {
if repo == nil {
return nil, errors.New("repo must not be nil")
}
return &OrderService{repo: repo, logger: logger}, nil
}
Variables
Go favours short variable names for short-lived, locally scoped variables. Name length should be proportional to scope:
// ✅ Short names for short-lived variables
for i, v := range items {
process(v)
}
// ✅ Descriptive names for wider scope
func processOrders(ctx context.Context, orders []*Order) error {
for _, order := range orders {
if err := validateOrder(order); err != nil {
return fmt.Errorf("validating order %d: %w", order.ID, err)
}
}
return nil
}
Common idiomatic short names:
| Variable | Meaning |
|---|---|
i, j, k |
Loop indices |
n |
Count or number |
v |
Value (in range loops) |
k |
Key (in map range loops) |
r, w |
io.Reader, io.Writer |
ctx |
context.Context — always ctx |
err |
Error return value — always err |
buf |
Buffer |
b |
[]byte slice |
mu |
sync.Mutex |
wg |
sync.WaitGroup |
Constants and iota
Constants follow the same MixedCaps / mixedCaps rule. Use iota for enumerations:
// ✅ Exported constants — MixedCaps
const (
MaxRetryCount = 3
DefaultTimeout = 30 * time.Second
DefaultPageSize = 20
)
// ✅ iota for ordered enumerations
type OrderStatus int
const (
StatusPending OrderStatus = iota // 0
StatusConfirmed // 1
StatusShipped // 2
StatusDelivered // 3
StatusCancelled // 4
)
// ✅ iota with bit shifts for flags
type Permission uint
const (
PermRead Permission = 1 << iota // 1
PermWrite // 2
PermExecute // 4
PermAdmin // 8
)
// ✅ iota with a typed string constant — use string constants instead of iota
// when the value must be human-readable (e.g. JSON, database)
type OrderStatusStr string
const (
StatusPendingStr OrderStatusStr = "pending"
StatusConfirmedStr OrderStatusStr = "confirmed"
StatusCancelledStr OrderStatusStr = "cancelled"
)
// ❌ UPPER_SNAKE — not Go convention
const MAX_RETRY_COUNT = 3
Error Variables
Sentinel errors use the Err prefix. Custom error types use the Error suffix:
// ✅ Sentinel errors — Err prefix
var (
ErrNotFound = errors.New("not found")
ErrUnauthorised = errors.New("unauthorised")
ErrInvalidInput = errors.New("invalid input")
ErrAlreadyExists = errors.New("already exists")
)
// ✅ Custom error type — Error suffix
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %q: %s", e.Field, e.Message)
}
type NotFoundError struct {
Resource string
ID int64
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with id %d not found", e.Resource, e.ID)
}
Method Receivers
Receiver names must be short — typically one or two letters abbreviated from the type name. Use the same receiver name consistently across all methods of a type:
// ✅ Short, consistent receiver name
type OrderService struct { ... }
func (s *OrderService) GetOrder(ctx context.Context, id int64) (*Order, error) {}
func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error) {}
func (s *OrderService) CancelOrder(ctx context.Context, id int64) error {}
// ❌ Verbose receiver names — not Go idiom
func (orderService *OrderService) GetOrder(...) {}
func (this *OrderService) GetOrder(...) {} // 'this' is not Go idiom
func (self *OrderService) GetOrder(...) {} // 'self' is not Go idiom
this and self are not Go
Using this or self signals unfamiliarity with Go to reviewers. The convention is a short abbreviation of the type name.
Type Aliases and Defined Types
Use defined types (type X Y) to add methods or domain meaning. Use type aliases (type X = Y) only for genuine aliasing — migration, bridging packages:
// ✅ Defined type — adds domain meaning and allows methods
type UserID int64
type OrderStatus string
type Money struct {
Amount decimal.Decimal
Currency string
}
// Defined types can have methods
func (id UserID) IsValid() bool { return id > 0 }
func (s OrderStatus) IsTerminal() bool {
return s == StatusDelivered || s == StatusCancelled
}
// ✅ Type alias — only when bridging two packages or for migration
type LegacyOrderID = int64 // alias: LegacyOrderID IS int64, not a new type
// ❌ Using type alias where a defined type would add clarity
type UserID = int64 // just use int64 if you're not adding methods or constraints
Embedded Types
When embedding a type to promote its methods, do not name the embedded field — the type name serves as the field name:
// ✅ Embedded type — promotes all exported methods of the embedded type
type TimestampedEntity struct {
CreatedAt time.Time
UpdatedAt time.Time
}
type Order struct {
TimestampedEntity // embedded — Order gains CreatedAt and UpdatedAt
ID int64
UserID int64
Status OrderStatus
}
// Access via the outer struct directly:
order.CreatedAt // promoted field
order.UpdatedAt // promoted field
// ✅ Embedding an interface in a struct (for test doubles)
type MockOrderRepository struct {
OrderRepository // embed interface — panic on unimplemented methods
findByIDFunc func(ctx context.Context, id int64) (*Order, error)
}
func (m *MockOrderRepository) FindByID(ctx context.Context, id int64) (*Order, error) {
if m.findByIDFunc != nil {
return m.findByIDFunc(ctx, id)
}
return m.OrderRepository.FindByID(ctx, id)
}
Test Function Naming
Go test functions use the pattern Test<FunctionName> for simple tests and Test<FunctionName>_<Scenario> for table-driven or scenario-specific tests:
// ✅ Simple test
func TestCalculateTotal(t *testing.T) {}
// ✅ Scenario-specific tests
func TestCreateOrder_WhenCartIsEmpty_ReturnsError(t *testing.T) {}
func TestCreateOrder_WithValidItems_ReturnsOrder(t *testing.T) {}
func TestGetOrder_WhenNotFound_ReturnsErrNotFound(t *testing.T) {}
// ✅ Benchmark
func BenchmarkCalculateTotal(b *testing.B) {
for i := 0; i < b.N; i++ {
calculateTotal(items)
}
}
// ✅ Example — appears in godoc
func ExampleOrderService_CreateOrder() {
svc := NewOrderService(repo, logger)
order, err := svc.CreateOrder(ctx, req)
if err != nil {
log.Fatal(err)
}
fmt.Println(order.Status)
// Output: pending
}
File Names
Go source files use snake_case (lowercase with underscores):
✅ Correct file names
order_service.go
http_client.go
payment_processor.go
order_service_test.go ← test files always end in _test.go
| Suffix | Purpose |
|---|---|
_test.go |
Test file — compiled only during go test |
_linux.go |
Build constraint — Linux only |
_windows.go |
Build constraint — Windows only |
_amd64.go |
Build constraint — AMD64 architecture only |