Last Updated: May 22, 2026
Go cares about how you name things and how you write comments. The compiler enforces the rules around visibility, the formatter enforces the rules around layout, and the standard tools turn well-placed comments into documentation you can browse online. This lesson covers the syntax for comments and the naming conventions every Go file is expected to follow.
Go has two ways to write a comment, and they behave the same as in many other languages.
In day-to-day Go code, line comments are the norm. Block comments show up mostly at the top of a file for package-level documentation, and occasionally to temporarily disable a chunk of code. There's no separate "doc comment" syntax. A regular line comment placed in the right spot becomes documentation, which we'll get to in a moment.
A comment placed directly above a declaration becomes a doc comment for that declaration. The go doc command and the pkg.go.dev website read those comments and turn them into the documentation other developers see.
The convention has two parts. The comment sits immediately above the declaration with no blank line in between, and the first sentence starts with the identifier name.
If you run go doc CalculateDiscount against this file's package, the tool prints the function signature followed by the comment above it. The pkg.go.dev site shows the same comment as the function's description. That's why the first word matches the identifier: when the comment is lifted out of the source and shown next to the name, it reads as a complete sentence.
A blank line between the comment and the declaration breaks the link.
The tools treat that as a free-floating comment and ignore it. Keep the comment glued to the declaration.
A package can have its own doc comment too. It goes at the top of one file in the package, directly above the package line, and describes what the package does as a whole.
You only need this in one file per package. If two files both have a package doc comment, the tools concatenate them, but the convention is to pick one file (usually doc.go or whichever file has the most central types) and put the package summary there.
The same "starts with the identifier" rule applies. For packages, the identifier is the word Package followed by the package name.
When you want to keep an old function around but steer users toward something new, mark it with a Deprecated: line inside the doc comment. The tools and most editors will surface this as a warning.
A blank comment line separates the main description from the Deprecated: paragraph. The word Deprecated: must be at the start of a line, capitalized exactly as shown, followed by a colon and a space. Tools like staticcheck and the Go language server look for that exact form when they flag callers of the old function.
Go's spec is explicit about how identifiers should be cased. Multi-word names use MixedCaps. Underscores and ALL_CAPS are out, even for constants.
The non-idiomatic versions still compile. Go is a friendly compiler about names. But every linter, every formatter, and every reviewer will push back. gofmt doesn't rename identifiers for you, but golint and staticcheck flag underscores in identifiers as style violations.
The reason for MixedCaps is partly aesthetic and partly that Go uses the case of the first letter for something else, which is the next topic.
In Go, you control whether something is visible outside its package by capitalizing its first letter. No public, private, or internal keywords. The case of the first letter is the access modifier.
The rule applies to every named entity: types, functions, methods, variables, constants, and struct fields. Capitalize the first letter and the symbol is part of your package's public API. Use lowercase and it's an implementation detail.
This has a real consequence for how you design types. If a struct's field starts with lowercase, code in other packages can't read or write it directly. They have to go through whatever exported method you provide. That's the standard way to keep invariants safe.
The diagram shows what code outside the cart package can and can't reach. Order and Number are visible. The lowercase total field is hidden, so the importer has to use whatever method the package exposes to read or modify it.
When an identifier contains an acronym (HTTP, URL, ID, JSON, SQL), keep all letters of that acronym in the same case. Don't switch to title case in the middle.
If the acronym is at the start and the identifier is unexported, the whole acronym is lowercase (httpClient, urlPath). If the identifier is exported, the whole acronym is uppercase (HTTPServer, URLPath). The mix-case version (HttpServer, UrlPath) is the most common style violation in Go code written by people coming from Java or C#.
staticcheck flags these under the rule ST1003, so a linter will catch them in practice.
Go programmers use short names for short-lived variables. The shorter the scope, the shorter the name should be.
There's an idiom for short names that you'll see everywhere in Go code: i and j for loop indices, n for a count, err for an error, r for a reader, w for a writer, b for a byte slice or builder, s for a string, k and v for key and value. Inside a tight loop or a five-line function, these read fine. The reader can hold the context in their head.
The flip side is that package-level names and names that live across many lines should be descriptive. A variable that survives 40 lines of logic should not be called t. Call it cartTotal or orderTotal so the reader doesn't have to scroll back to remember what it holds.
A method's receiver gets a short name, one or two letters, taken from the type itself. Use the same receiver name across every method on the same type.
Receiver names like self or this are non-idiomatic in Go. You won't see them in the standard library, and Go's documentation tools assume the short form. Pick one letter (usually the first letter of the type, lowercased) and use it every time. Switching between o and order on different methods of the same type is a small thing that adds reader friction.
A single-method interface in Go typically takes its name from the method it requires, with an -er suffix.
The standard library is full of these: io.Reader has Read, io.Writer has Write, io.Closer has Close, fmt.Stringer has String, sort.Interface has three methods so it doesn't fit the pattern and uses a generic name instead. The convention is helpful when it fits the shape of the interface. Don't force it when the interface has multiple methods that don't summarize cleanly into one verb.
Package names are lowercase, short, and a single word. No underscores, no mixedCaps, no plurals.
The package name is part of how callers write code. cart.Total() reads better than shoppingCart.Total() or carts.Total(). Pick a short noun that describes what's in the package.
Generic names like util, common, helpers, or misc are an anti-pattern. They tell the reader nothing about what's inside, they tend to attract unrelated code over time, and they encourage circular imports because everything depends on util. If you have a few small helper functions, put them in the package that uses them. If they really are general-purpose, find a more specific name (strslice, timefmt, mathx) that describes the actual functionality.
Cost: A package called util tends to grow without bound. Every refactor adds another helper, and before long the package is depended on by everything else in the project. Splitting it later means changing import paths in every file that touched it. Pick specific names up front.
Constants follow the same naming rules as variables. No SCREAMING_SNAKE_CASE, even though many languages use it.
A constant is just a named value. The same export rule applies: capitalize for public, lowercase for private. The visual cue of ALL_CAPS that other languages use to say "this is a constant" isn't needed in Go because the value is fixed at compile time and the compiler will catch any attempt to assign to it.
For sentinel error values (errors that callers compare against to check for a specific failure), the convention is to start the name with Err.
Custom error types (structs that implement the error interface) follow a different convention. They're named with an Error suffix instead, like *os.PathError or *json.SyntaxError. For now, the rule to remember is that a sentinel error value starts with Err.
A small package that breaks several naming conventions, with comments to spot the issues:
It compiles and runs, but a Go reviewer would push back on most of it. Here are the problems:
customer is unexported but its consumer might need it. If this is meant to be the public type, it should be Customer. If it's truly internal, the lowercase is fine, but then Name and email have inconsistent casing for no reason.MAX_DISCOUNT uses SCREAMING_SNAKE_CASE. It should be MaxDiscount.Calculate_Cart_Total uses underscores. It should be CalculateCartTotal.discount_percent and cart_total also use underscores. They should be discountPercent and cartTotal.// this function returns the total doesn't start with the function name and isn't very informative. The doc comment should be // CalculateCartTotal returns the discounted total of the cart.index := 0; index < len(prices); index++ is verbose. A for _, p := range prices would read better and avoid an unused index name.Fix:
Every name now follows MixedCaps. The exported type and function start with a capital letter, the helper variable inside the function is lowercase, and the doc comments start with the identifier name.
Here's a quick reference for the conventions we just walked through.
| Category | Convention | Good Example | Bad Example |
|---|---|---|---|
| Local variable | MixedCaps, short in small scope | cartTotal, i, err | cart_total, index_pos |
| Function | MixedCaps, exported starts uppercase | CalculateDiscount, parseURL | Calculate_Discount, parseUrl |
| Struct type | MixedCaps, exported starts uppercase | Order, Customer | order_struct, customerInfo |
| Struct field | MixedCaps; case controls export | Number, total (private) | order_number, OrderID if you meant private |
| Interface (single method) | Method name + -er | Reader, Stringer | IReader, ReaderInterface |
| Receiver | 1-2 letters, consistent per type | (o *Order), (c *Customer) | (self *Order), mixing o and order |
| Package | lowercase, single word, no underscores | cart, order, payment | shopping_cart, Util, helpers |
| Constant | MixedCaps; case controls export | MaxItems, defaultDiscount | MAX_ITEMS, DEFAULT_DISCOUNT |
| Sentinel error | Err + descriptive name | ErrOutOfStock, ErrInvalidQuantity | OutOfStockError, STOCK_ERROR |
| Acronyms | Same case throughout | HTTPServer, parseURL | HttpServer, parseUrl |
A note before wrapping up. Go ships with gofmt, a tool that reformats source code to the canonical Go style. It handles indentation, spacing, import grouping, and brace placement. The community decision to standardize on gofmt removes a whole class of style arguments from code review.
Most editors run gofmt (or its richer cousin goimports) on save, so you rarely invoke it by hand. The relevant point for this lesson is that gofmt enforces layout (whitespace, braces, alignment) but not naming. It won't rename a variable from cart_total to cartTotal. That's a job for the reviewer or a stricter linter like staticcheck.