In Go, why is a type named http.HTTPClient worse than http.Client?
answer
- names are read with the package prefix
- say the call site out loud
- the package already said HTTP
- bufio.NewWriter, never bufio.NewBufioWriter
basics
~20 sEvery reference outside the package already carries the package name, so http.HTTPClient says HTTP twice at the call site. Go names a type for how it reads after the qualifier, so inside package http the type is simply Client.
solid answer
~50 sIn Go the package name is the first word of every identifier you write outside the package, so you name a type for the call site rather than for the file it sits in. Inside `package http` the type is `Client`, and callers read `http.Client`; naming it `HTTPClient` forces them to read `http.HTTPClient`, which repeats a word that carries no extra information. The same rule shapes constructors: a package with one main type exports `New`, so `container/list` gives you `list.New()`, not `list.NewList()`, and a package with several types qualifies only the part that differs — `bufio.NewReader` and `bufio.NewWriter`. The standard library is the model throughout: `bytes.Buffer`, `strings.Builder`, `bufio.Scanner`, `time.Duration`. It is a readability convention, not a compiler rule — nothing in `go build`, `gofmt` or `go vet` rejects a stutter, so review is what catches it.
code
go · 10 lines// package bufio
type Reader struct{ /* ... */ }
type Writer struct{ /* ... */ }
func NewReader(rd io.Reader) *Reader
func NewWriter(w io.Writer) *Writer
// call sites read as phrases:
// bufio.NewReader(f)
// var w *bufio.Writergo deeper
Be ready to state the mechanic in one line: in Go you almost always write the package name before an exported identifier, so name the identifier for that combined form. Have one standard library example ready, such as bytes.Buffer.
Explain how the rule extends to constructors — New for a package with one main type, NewReader and NewWriter where several exist — and be able to say that nothing in the toolchain enforces any of it.
Show judgement about the exceptions. Know that container/list really is list.List, and be able to argue when a slightly repetitive name is the honest one versus when it is a symptom of a package that was never given a real subject.
Frame it as an interface cost you cannot take back cheaply: an exported name is read at every call site in every consuming repo, so a rename after publication is a coordinated migration. Naming reviews belong before the first tag, not after.
## What "stutter" means A stuttered identifier is one that repeats, inside the name, information the reader already has from the package qualifier. `http.HTTPClient`, `chart.ChartRenderer`, `config.ConfigLoader` and `user.UserService` all stutter: the word before the dot and the first word after it say the same thing. The reason this matters more in Go than in many languages is mechanical. In Go, you almost never refer to another package's exported identifier without the package name in front of it. There is no `using`-style wholesale import that drops the qualifier (dot-imports exist but are reserved for a couple of testing situations and are strongly discouraged). So the *name a reader actually sees* is `package.Identifier`, and that pair is the unit you are designing. If you name the type in isolation, staring at the file it lives in, you will reach for `HTTPClient` because the file does not say "http" anywhere. If you name it by writing the call site down first, you get `Client`. ## The rule, stated for the call site Name the exported identifier so that `package.Identifier` reads as a short phrase with no repeated word: - `bytes.Buffer`, not `bytes.ByteBuffer` - `strings.Builder`, not `strings.StringBuilder` - `time.Duration`, not `time.TimeDuration` - `bufio.Scanner`, not `bufio.BufioScanner` The same reasoning applies to functions and constructors. A package whose whole job is one type exports `New` as the constructor, so the call site is `list.New()` rather than `list.NewList()`. When a package builds several distinct things, qualify only the distinguishing part: `bufio.NewReader` and `bufio.NewWriter`, `sql.Open`, `errors.New`. Notice that none of these names would be complete on their own — `New` alone means nothing — and that is exactly the point: the package supplies the missing half, at every use. ## The mirror image: don't strip the name so far that the call site is meaningless The convention is not "shorter is always better". It is "remove the redundancy, keep the information". If a package exports three unrelated constructors, calling them `New`, `New2` and `NewOther` is worse than a stutter — the call site now has less information, not less noise. The test is always the same: write the call site down and read it aloud. `chart.Renderer` reads well. `chart.ChartRenderer` reads badly. `chart.R` reads badly too. ## Nothing enforces this The Go compiler is entirely indifferent. `go build` compiles a stuttering name without comment, `go vet` looks for correctness bugs rather than naming style, and `gofmt` reformats whitespace and structure but never rewrites an identifier. That means stutter is a review-time concern, and it is one of the most common comments a Go newcomer receives on a first pull request — particularly from engineers arriving from languages where the class name has to be globally unique and self-describing because it will appear bare in the source. ## Where the standard library itself stutters Be honest about the exceptions in an interview, because they exist. `container/list` exports a type `List`, so the call site really is `list.List`. `container/ring` gives you `ring.Ring`. These are packages whose entire purpose is one data structure, where there is no shorter honest noun; and even there the constructors are `list.New` and `ring.New`, not `list.NewList`. The convention is a readability heuristic applied by judgement, not a lint rule with no exceptions. ## The related move: import aliases A follow-on question is what to do when two packages you import share a short final element — the classic pair being `math/rand` and `crypto/rand`. The answer is not to rename either package to something longer and more "unique". The importing file resolves it locally with an alias, for example importing `crypto/rand` as `crand`, which keeps the collision handling in the one file that has the problem rather than pushing an ugly name onto every other caller in the world. That asymmetry — cost paid by the importer with the clash, not by the package — is the same principle that drives the stutter rule: the package is written once and read at thousands of call sites, so the call site wins.
- The standard library ships container/list with a type named List. Does that break the rule?It bends it, and it is worth saying so. `list.List` and `ring.Ring` are packages whose entire purpose is one data structure, where no shorter honest noun exists. Even there the constructor is `list.New`, not `list.NewList`. The convention is a readability heuristic applied with judgement, not an absolute — but a new package that stutters usually has a better noun available.
- How do you name constructors in a package that exports several types?One dominant type gets `New`, so callers write `pkg.New()`. When several types need constructing, qualify only the distinguishing part: `bufio.NewReader` and `bufio.NewWriter`, never `bufio.NewBufioReader`. The rule is unchanged — you are naming the string `package.Identifier`, and every word in it should earn its place.
- What do you do when two imported packages have the same short name, such as math/rand and crypto/rand?Alias one of them in the importing file, for example importing `crypto/rand` as `crand`. You do not lengthen either package's own name. The file with the collision pays for it locally; every other caller keeps the short, clean qualifier. A package is written once and read at thousands of call sites.
A file in a folder named invoices does not need to be called invoice-invoice-2024.pdf. The folder is part of the path you read.
saying these in an interview costs you the question
- Says a longer name is always clearer than a shorter one
- Names the type after its package or its file rather than the call site
- Claims go vet or gofmt rejects a stuttering identifier
- Wants NewHTTPClient inside package http so it is easier to grep
- Strips names so far that the call site loses information