In Go, why are one-letter locals like i, r and buf idiomatic rather than sloppy?
answer
- name length tracks scope length
- how far from the declaration is it read?
- w and r in every handler signature
- io.Copy's parameters are dst and src
basics
~20 sGo sizes a name to its scope. A variable declared and used within a few lines needs only enough letters to be unambiguous there, because its declaration is visible; a name read far from its declaration, such as an exported one, gets a full word.
solid answer
~50 sGo's convention is that name length tracks scope length: the further a reader is from the declaration when they meet the name, the more the name has to carry on its own. In a three-line loop the declaration is on screen, so `i`, `b` or `buf` is unambiguous and the extra words in `currentIndex` are noise. The standard library follows this in its own signatures — `io.Copy` is declared `Copy(dst Writer, src Reader) (written int64, err error)`, and handlers everywhere take `(w http.ResponseWriter, r *http.Request)`. Package-level and exported identifiers are the opposite case: they are read in other packages, sometimes years later, so they get a real word. There are two extra Go-specific pressures. `err` is a fixed convention for the immediate error value, and the package qualifier already supplies context at the call site, so identifiers inside a well-named package can afford to be short.
code
go · 8 lines// io
func Copy(dst Writer, src Reader) (written int64, err error)
// a typical handler
func handleHome(w http.ResponseWriter, r *http.Request) {
n, err := io.Copy(w, r.Body)
_, _ = n, err
}go deeper
Know the shorthand vocabulary you will meet in every Go file: i and j for indexes, n for a count, b for a byte, s for a string, ctx for a context, err for an error, and w and r in handler signatures.
Be able to state the rule as a proportion — name length tracks the size of the scope it is read in — and justify it with a standard library signature rather than personal taste.
Apply it as a review heuristic in both directions: push back on a long name in a three-line scope and on a one-letter name in an exported identifier, and be able to say why the second is the more serious defect.
Decide how much of this a team codifies. Naming conventions that the toolchain cannot enforce survive only through review culture and examples, so know what you write down and what you leave to taste.
## The rule Go's community convention, visible throughout the standard library, is that the length of a name should be roughly proportional to the size of the scope in which it is read, and inversely proportional to how often it appears in that scope. A variable that lives for three lines wants one or two characters. A variable that lives for a page wants a word. An exported package-level identifier, read in other repositories by people who will never see its declaration, wants a name that is complete on its own. This surprises engineers arriving from codebases where `for (int index = 0; ...)` is house style and a one-letter name reads as carelessness. In Go it is the opposite: the short name is deliberate, and a long name in a tiny scope is the thing reviewers comment on, because it adds reading work without adding information. ## Why short scopes can afford short names A name has one job — to let the reader recover what the value is. In a short scope, the declaration itself does that job: ``` for i, b := range data { if b == '\n' { return i } } ``` The reader learns from the `range` line that `i` is the index and `b` is the byte. Renaming them `byteIndex` and `currentByte` does not add a single fact; it just makes the three lines longer. If the loop body were forty lines, or if two nested loops both had indexes into different collections, the calculus would change and fuller names would earn their keep. ## The standard library's own signatures Go's authors applied this to the public API, which is why you can read the convention straight off the documentation. `io.Copy` is declared `Copy(dst Writer, src Reader) (written int64, err error)` — `dst` and `src` because the whole scope is one signature plus a short body. HTTP handlers universally take `(w http.ResponseWriter, r *http.Request)`, and a reviewer would find `responseWriter` and `httpRequest` odd rather than clearer. Common short names have effectively become vocabulary: `i` and `j` for indexes, `n` for a count or number of bytes, `b` for a byte or a `[]byte`, `s` for a string, `buf` for a buffer, `w` and `r` for a writer and a reader (or a response writer and a request), `ctx` for a `context.Context`, and `err` for an error. ## The other end of the scale The same rule, run the other way, forbids one-letter names in wide scopes. A package-level variable, an exported function, a struct field or a constant that other packages read must be self-explanatory, because the reader is nowhere near the declaration and may be in a different repository entirely. `RequestCount` is right where `c` would be indefensible. Note also that the package qualifier is doing work here: because callers read `http.Get` rather than a bare `Get`, an exported name inside a well-named package can stay shorter than it would need to be in a language where the identifier stands alone. ## Errors and the err convention `err` is not an abbreviation to be improved on; it is the convention for the error value in hand, and code that names it `e`, `error1` or `theError` reads as foreign. Where two errors are live at once — say a work error and a deferred close error — name them for their source, such as `readErr` and `closeErr`, rather than `err1` and `err2`. ## What this is not It is not a licence to write cryptic code. The test is whether a reader who starts at the declaration and reads forward can hold the meaning without effort. If a short name forces anyone to scroll back, it is too short — and nothing in the toolchain will tell you, because name length is not something the compiler or `go vet` has an opinion about. Like the rest of Go's naming conventions, it is enforced entirely in review.
- When is a one-letter name wrong in Go?When the scope stops being short. A forty-line function body, two nested loops indexing different collections, a struct field, or any exported package-level identifier all put distance between the declaration and the use, so the name has to carry meaning by itself. The question is never the character count; it is whether the reader can recover the value without scrolling back.
- Should an error variable ever be called something other than err?`err` is the convention for the error value currently in hand, and deviating reads as foreign. The exception is when two errors are live at once — for example a work error and one from a deferred close — where naming them for their source, such as `readErr` and `closeErr`, beats `err1` and `err2`.
- Does the same short-name rule apply to struct fields?No. A struct field is read wherever a value of that type travels, which for an exported type means other packages entirely, so it needs a full name. The short-name licence belongs to locals and parameters whose declaration is visible from the use.
saying these in an interview costs you the question
- Calls all one-letter names sloppy regardless of scope
- Renames loop indexes to currentIndex for readability
- Uses one-letter names for exported package-level identifiers
- Names the error value e or error1 instead of err
- Thinks a linter or gofmt decides name length