Package Naming in Go and the Names You Should Avoid
Good Go package names are short, lowercase and singular, and make call sites read well. See the stutter test and why util and common fail.

You pick a package name once and every user of the package types it hundreds of times. That makes it the cheapest piece of an API to get right and the most annoying to change. The Go community has settled on a short set of conventions, and they come down to one test: read the call site aloud. If it sounds like a sentence, the name is working. This article lists the rules, runs the stutter test on real examples, explains why the generic names fail, and covers the cases where the rules bend. It follows packages and exported names, and the naming conventions reference covers identifiers beyond packages. It sits in the Go basics path.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
The rules
Short, lowercase, one word. strings, bytes, sort, flag, http, json. No underscores, because user_store is a variable-style name that breaks the lowercase look of every import block, and no mixed case, because the import path is the directory name and directory names are lowercase by convention on every filesystem. The package name should match the last element of the import path. A directory called httputil holds package httputil, and a directory user holds package user.
Singular, usually. A package named user that holds the User type and its operations reads well, and users reads like a collection. The standard library does have plural names, strings, bytes, errors, and in each case the package is a toolbox of functions for a thing whose type is named in the singular, the string or the error. So for a package of helpers for a type that is not a builtin, the singular is the rule.
Abbreviate only when the abbreviation is understood by every reader. fmt, os, io, bufio, strconv and regexp are fine because they are in every Go programmer's head. An abbreviation you invented is not.
The call-site test
Everything exported from a package is used with the package name in front, so the name is a prefix that you do not have to repeat. The standard library is the model:
package main
import (
"bytes"
"fmt"
"strings"
"time"
)
func main() {
var buf bytes.Buffer
buf.WriteString(strings.ToUpper("read it aloud"))
timeout := 5 * time.Second
fmt.Println(buf.String(), timeout)
}READ IT ALOUD 5sbytes.Buffer, strings.ToUpper, time.Second: each reads as "the buffer from bytes", "to-upper from strings", "the second from time". Compare the stuttering versions, which you may have written:
| Poor | Better | Why |
|---|---|---|
http.HTTPServer | http.Server | The package already says HTTP |
user.UserService | user.Service | user. supplies the noun |
config.ConfigReader | config.Reader | Same |
store.NewStore() | store.New() | The type has the package's name, so New is enough |
log.LogError | log.Error | The prefix is redundant |
api.APIClient | api.Client | Stutter |
A name that stutters is easy to fix in the package, and expensive to fix after others import it. The rule I apply when I read my own code is to say each qualified name out loud and listen for the same word twice. There is one common exception: when the package's main type is the package's name and nothing else, store.Store, a constructor store.New() returns one, and the pattern is well known from list.List and ring.Ring in the standard library, even if newer packages avoid it by naming the type for what it does.
Names that tell the reader nothing
util, utils, common, helpers, misc, shared, base, lib, core, types and models are the names people reach for when a package has no purpose. They fail three ways.
They hide what is inside. util.Parse could parse anything, and the reader has to open the file to find out. They collect unrelated code, because anything that has no obvious home gets dropped there, so the package grows without bound and everything imports it. And they cause cycles, because a package that everything imports cannot import anything that imports it.
The fix is to name a package for what it does for a caller, and to break up a grab bag by purpose. A util package with string helpers, a retry function and a time formatter is three packages, or three functions that belong next to their only users. stringsx, retry, timefmt is already better, and in many cases the helper should live in the package that calls it, unexported. A package called models that holds every struct in the application has the same problem from the other side. It collects types by their kind and not by their job, so every other package imports it. Put each type next to the code that owns its behavior. The Go blog's article on package names makes the same argument with the standard library's own examples, including why util was never a good idea there either.
Names and import paths
The package name and the import path are separate things that usually agree. The import path decides where the files are, and the package name decides what you type. A few cases where they differ:
- Version suffixes.
math/rand/v2has package namerand, andgithub.com/you/lib/v3has package namelib. The/v2is part of the path so the module system can tell versions apart, and the name stays the same. - Main packages. Any directory with
package mainbuilds a program, whatever the directory is called. - Renamed imports. If two imports have the same package name, such as
crypto/randandmath/rand, alias one at the import site:mrand "math/rand". Aliases should be short and consistent across your code base. - Test packages. A directory may also hold
package user_testfiles, which exercise the public API from outside. That one underscore is allowed and required, since the_testsuffix is the mechanism.
If a name is genuinely hard to choose, check how it will read when imported next to others. A package named log will collide with the standard log in files that import both. A name like errors or context invites confusion with the standard package, so avoid reusing them unless your package really replaces the standard one, as some error-handling libraries do.
A quick audit of a code base
You can find the worst offenders mechanically. List the package names and look at the ones that do not describe a noun:
go list -f '{{.Name}} {{.ImportPath}}' ./... | sortRead the first column. Anything named util, common or helpers is a candidate for splitting. Then grep for stutter: exported names that begin with the package name, such as user.UserFoo. The revive linter has rules for both. It is called var-naming for general identifiers and exported for the stutter check, and staticcheck carries a related style check. Run one of them in CI so that naming is checked by a tool and not a reviewer.
When the rules bend
Domain names sometimes demand the plural: orders can be the clearest name for a package that owns the order lifecycle, and the lowercase singular order might collide with a local variable. If the plural reads better in the code, use it, and be consistent across the project. Generated code often has names that follow the generator's pattern, such as api or pb for protocol buffers, and those names are fine. For internal packages that only your team imports, a slightly longer name with the project prefix is acceptable if it prevents a clash. What does not bend is the test of reading the call site aloud, and the habit of choosing a name that says what the package is for. The official reference is Effective Go on package names, and the Go Code Review Comments on package names adds the specific bans.


