I had noticed Google’s Go style guide a while ago, but only recently sat down to read it carefully. A few points stood out because they touched habits I’d picked up over time without really questioning them. Most of the usual formatting issues are already handled by gofmt, goimports, linters, and IDE support, so the more interesting parts are the naming choices and API design details that can easily slip by.
Here are the parts that felt most worth keeping in mind.
Keep package names all lowercase
One rule that is easy to overlook is that package names should not use underscores. If a package name needs to express two words, it still stays lowercase and compact.
That means a package like tabwriter should not be written as tabWriter, TabWriter, or tab_writer.
I used to lean toward underscores in these cases for two reasons. One was old naming habits shaped by C. The other was readability: two words smashed together in lowercase can feel harder to scan at first glance. Even so, this guideline is straightforward, and it is one of those things worth paying attention to consistently.
Avoid Get in method names
Another naming rule that hits common habits directly: functions and methods generally should not start with Get or get, unless the underlying concept literally uses that word, such as HTTP GET.
In many cases, a noun is the better choice. For example, Counts is preferred over GetCounts.
If the method does more than simply return a field—say it performs a costly computation or makes a remote call—then names like Compute or Fetch are better because they signal that the call may take time, block, or fail.
This is the kind of rule that immediately exposes names like GetUserByID, which I had written plenty of times before. It is a small change, but it improves how an API reads.
Error strings should stay plain
Error messages in Go should not start with a capital letter, unless the text begins with an exported name, a proper noun, or an acronym. They also should not end with punctuation.
Linters often catch this, and in practice it is surprisingly easy to get wrong—especially when code is generated with tools that like to append periods automatically.
A related detail that I had not paid enough attention to is %q in Go’s formatting functions such as fmt.Printf. It prints a string wrapped in double quotes.
1 2 3 4 5 6</th>
<th>// Good: fmt.Printf("value %q looks like English text", someText) // Bad: fmt.Printf("value \"%s\" looks like English text", someText) // Avoid manually wrapping strings with single-quotes too: fmt.Printf("value '%s' looks like English text", someText)</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
I usually did not manually wrap strings in quotes, but I often wrote things like [%s] when logging values. The reason was simple: if the value is an empty string or just whitespace, it is hard to see that in plain output. %q is a cleaner way to make that visible.
Use value receivers for maps, functions, and channels
When the receiver type is a map, function, or channel, the guideline is to use a value receiver rather than a pointer receiver.
1 2 3 4 5</th>
<th>// Good: // See https://pkg.go.dev/net/http#Header. type Header map[string][]string func (h Header) Add(key, value string) { /* omitted */ }</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
This was not something I had thought about carefully before. In many cases, my choice between value and pointer receivers was overly casual, usually focused only on whether a struct needed to be modified. This rule is a useful reminder that receiver choice is not just about mutation.
Watch out for repetitive names
Go style also discourages redundant naming.
Common examples include:
- repeating the package name:
widget.NewWidgetwidget.New- repeating the receiver context:
func (p *Project) ProjectName() stringfunc (p *Project) Name() string- repeating parameter meaning in the function name:
func OverrideFirstWithSecond(dest, source *Config) errorfunc Override(dest, source *Config) error- repeating return type information in the function name:
func TransformYAMLToJSON(input *Config) *jsonconfig.Configfunc Transform(input *Config) *jsonconfig.Config
That said, Go does not support method overloading the way Java does, so sometimes names do need extra words to distinguish behaviors that are similar but accept different inputs. In those cases, repetition is not just acceptable—it can be necessary.
For example:
<table> <thead> <tr> <th>1 2</th>
<th>func (c *Config) WriteText(s string) func (c *Config) WriteNumber(num int)</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
So the rule is not “never repeat words under any circumstance,” but rather “don’t repeat information unless it is actually doing useful work.”
Prefer zero-value declarations for unmarshaling
For deserialization, the guide prefers declaring a zero value first and then unmarshaling into it.
<table> <thead> <tr> <th>1 2 3</th>
<th>// Good: var coords Point if err := json.Unmarshal(data, &coords); err != nil {</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
This one is mostly about habit. I often wrote coords := &Point{} before unmarshaling, probably because of an instinctive discomfort with nil pointers. The zero-value style is simpler and matches Go’s general preference for straightforward declarations.
Don’t reach for panic by default
For startup failures—such as not being able to read a configuration file—it is common to use panic because the program cannot continue anyway. A better option suggested here is to use Fatal from glog instead.
That makes sense. A fatal log communicates the failure clearly and exits the program without turning the situation into a panic unless a panic is truly what the program semantics require.
A lighter option for CLI subcommands
When building command-line tools, the standard flag package is often not enough if the interface needs subcommands.
Sometimes the goal is not this:
./cmd -flag1 -flag2
but this:
./cmd subcommand
In those cases, it is easy to jump straight to Cobra, and I have done that too. But when a tool only needs a small number of subcommands, bringing in a heavier framework can feel excessive.
github.com/google/subcommands is a useful middle ground. It is lightweight and makes it easier to implement subcommand-based CLIs without pulling in more machinery than necessary. For smaller utilities, it is a practical option to keep in mind.
What stood out most from all of this is that Go style is already fairly unified at the formatting level. The rough edges tend to show up elsewhere: naming, method semantics, receiver choices, and the little API decisions that shape how code feels to use. Those are also the parts most likely to survive linting unless someone is being deliberate about them.