Why does Go write a multi-word identifier as maxRetryCount rather than max_retry_count?
answer
- case is Go's word separator
- constants get no special spelling
- underscores live in file names
- the first letter already means something
- gofmt formats, it never renames
basics
~20 sGo's convention is MixedCaps: each following word is capitalised and underscores are dropped. Case is already meaningful, since the first letter decides package visibility, so it doubles as the word separator. Underscores belong to file names.
solid answer
~40 sGo names multi-word identifiers in MixedCaps or mixedCaps: `maxRetryCount`, `ParseRequest`, `deadLetterURL`. Underscores in identifiers are legal to the compiler but non-idiomatic, and no Go codebase you will read uses them. The convention has a structural reason: Go already gives the first letter of a name a meaning, so case is load-bearing rather than cosmetic, and using it as the word separator keeps names short and uniform. Nothing in the toolchain fixes a bad name for you — `gofmt` reformats layout and never renames identifiers, and `go vet` looks for correctness bugs, not casing. Underscores do have idiomatic homes in Go source: file names such as `handler_test.go` or `poll_linux.go`, and the blank identifier `_`. They just do not appear inside the names you declare.
code
go · 13 linesconst maxRetryCount = 3
type Account struct {
CreatedAt time.Time `json:"created_at"`
}
func parseDeadLetter(raw []byte) (*Account, error) {
var a Account
if err := json.Unmarshal(raw, &a); err != nil {
return nil, err
}
return &a, nil
}go deeper
Recall the rule and one example each way: maxRetryCount, not max_retry_count, and DefaultTimeout, not DEFAULT_TIMEOUT. Be ready to say that constants follow the same spelling as variables in Go.
Explain why case is the separator: the first letter's case already carries package visibility, so Go readers are parsing case anyway. Know that gofmt formats layout and never renames identifiers.
Show where the convention gets violated in real code — names copied in from database columns, JSON keys or environment variables — and that the fix is to canonicalise at the boundary and keep the external spelling in a tag or a query.
Own the position that naming is enforced by review and by generators, not by hope. If a team keeps leaking foreign spellings into Go identifiers, the durable answer is a boundary that canonicalises names once rather than a style paragraph nobody rereads.
## The rule Go writes multi-word identifiers in **MixedCaps** (also called CamelCase): every word after the first begins with a capital letter, and the words are simply run together with nothing between them. `maxRetryCount`, `parseRequestBody`, `DeadLetterQueue`, `serveIndex`. The exported form starts with a capital (`MaxRetryCount`), the unexported form with a lowercase letter (`maxRetryCount`) — but in both, the *joining* of words is done with case, never with an underscore. This applies to every kind of name you declare: local variables, parameters, struct fields, methods, functions, types, constants and package-level variables. It also applies to constants, which is worth calling out because engineers arriving from C, Java or Python often expect `MAX_RETRY_COUNT`. Go does not use SCREAMING_SNAKE_CASE for constants at all. A constant is named exactly like any other identifier: `MaxRetryCount` if it is exported, `maxRetryCount` if it is not. There is no visual marker distinguishing a constant from a variable in Go, and none is wanted. ## Why case rather than underscores The usual answer is "because that is the convention", and for a junior interview that is nearly enough. But there is a structural reason worth being able to give. In Go, the case of a name's first letter is not a style choice — it is part of the language. A name beginning with an uppercase letter is visible to other packages; a name beginning with a lowercase letter is not. That means every Go engineer is already reading the case of identifiers for meaning, on every line, all day. Once case carries information, using it as the word separator too is cheap: the reader is already looking at it. Adding underscores on top would produce names like `Max_Retry_Count`, where the reader has to parse two separators that mean different things. The secondary reason is length. Go's culture favours short names, especially in short scopes. Underscores make names longer without making them clearer. ## What the toolchain does and does not do This is the part candidates most often get wrong. `gofmt` is not a linter and not a renamer. It normalises whitespace, indentation, alignment, the placement of braces and the grouping of imports. It will happily format a file full of `user_id` fields and change nothing about them. `go vet` reports likely bugs — a `Printf` verb that does not match its argument, a lock copied by value, an unreachable return — and says nothing about identifier casing. So MixedCaps is enforced by **people**: code review, and the fact that the standard library is written that way and every Go engineer has read it. If your team wants machine enforcement, that is a linter question, not a toolchain one. In an interview, saying "gofmt will fix it" is a concrete factual error and a bad look, because it suggests you have never actually watched gofmt run on a badly named file. ## Where underscores legitimately live Underscores are not banned from Go source — they are banned from *identifiers you declare*. They appear in: - **File names.** `user_service.go`, `user_service_test.go`, and the build-constrained suffixes the `go` command understands, such as `poll_linux.go` or `asm_arm64.s`. The `_test.go` suffix and the `_GOOS`/`_GOARCH` suffixes are part of the toolchain's own naming grammar. - **The blank identifier `_`**, used to discard a value (`_, err := f()`), to import a package purely for its side effects, or to assert at compile time that a type satisfies an interface (`var _ io.Reader = (*myReader)(nil)`). - **Generated and machine-derived code**, where names sometimes mirror an external system (C symbols reached through cgo, for example). This is tolerated precisely because a human did not choose the name. ## Where the mistake usually comes from The recurring source is a name imported from somewhere else: a database column `created_at`, a JSON key `user_id`, an environment variable `MAX_RETRIES`. The Go answer is that the *external* name stays in its own spelling — in a struct tag, in a query, in an `os.Getenv` call — and the Go identifier is spelled the Go way. So a struct field is `CreatedAt time.Time` with a tag mapping it to the external `created_at`; it is never a field named `Created_At`. The second source is a script or generator emitting Go from another system's names. That is the same problem at scale, and the fix is the same: canonicalise the name into Go's spelling at the boundary, once, in the generator. ## What an interviewer is checking That you have internalised the convention rather than memorised it, that you know case is semantically load-bearing in Go, and that you do not believe a formatter will rescue you. A candidate who says "MixedCaps, no underscores, and note that constants follow the same rule — there is no `MAX_SIZE` in idiomatic Go" has answered fully in two sentences.
- Where do underscores legitimately appear in Go source?In file names — `user_service_test.go`, and the build-constrained suffixes such as `poll_linux.go` — and in the blank identifier `_`, used to discard a result, to import a package for its side effects, or to assert a type satisfies an interface. Machine-generated code that mirrors an external system's symbols is also tolerated. None of these are identifiers a human chose to declare.
- How would you name an exported constant for a default timeout?`DefaultTimeout`. Go has no separate spelling for constants — they follow the same MixedCaps rule as everything else, so `DEFAULT_TIMEOUT` marks the author as arriving from C or Java. If the constant should not leave the package, it becomes `defaultTimeout`.
- Does anything in the Go toolchain enforce this convention?No. `gofmt` normalises layout, indentation and import grouping but never renames an identifier; `go vet` looks for likely bugs, not casing. MixedCaps is held up by code review and by the standard library being written that way. Machine enforcement, if a team wants it, is a linter's job rather than the toolchain's.
saying these in an interview costs you the question
- Says gofmt will rewrite snake_case identifiers for you
- Thinks underscores in identifiers are a compile error
- Names constants MAX_SIZE in SCREAMING_SNAKE_CASE
- Mirrors database column spelling into Go struct field names
- Calls the convention purely cosmetic, missing that case controls visibility