Comments
A comprehensive guide to Go's comment syntax covering line comments, general block comments, doc comments, and tool directives embedded in comments.
What Comments Are
A comment is text in source code that the Go compiler completely ignores. Its only job is to speak to a human reader — the person maintaining the code, the teammate reviewing a pull request, or yourself six months later. Go gives you two ways to write comments, plus a documentation convention that turns certain comments into API documentation.
Comments serve program documentation. This is the language’s own phrasing, and it captures the core purpose: to explain what the code does and why, without affecting execution.
Line Comments
A line comment begins with the character sequence // and stops at the end of the line. Everything from // onward is invisible to the compiler.
// This entire line is a comment.
x := 10 // This is an inline comment on the same line as code.
Line comments are the most common form in Go source. They are short, unambiguous, and trivially easy to spot. In practice, most explanatory remarks and nearly all temporary code removals use //.
// TODO: replace this hard-coded limit with config value.
const maxRetries = 5
A line comment can appear on its own line or at the end of a line that contains code. In either case, the comment ends when the line ends. There is no way to continue a line comment across multiple lines; each new line needs its own //.
Line comments are the norm:
The Go ecosystem overwhelmingly prefers // over /* */ for general commentary. The block comment form is reserved almost entirely for package-level documentation headers and inline workarounds.
General (Block) Comments
A general comment starts with /* and stops at the first subsequent */. It can span multiple lines, and everything between the delimiters is ignored.
/*
This is a multi-line comment.
It spans several lines and the compiler
does not see any of this text.
*/
fmt.Println("hello")
The language specification calls these “general comments” rather than “block comments,” but the idea is the same. The key difference from line comments is that /* … */ can be placed in the middle of an expression or statement under specific conditions.
How General Comments Interact with Whitespace
The specification defines two important rules about whitespace behavior:
- A general comment that contains no newlines acts like a single space.
- Any other general comment — one that does contain a newline — acts like a newline.
This means you can place a short, single-line block comment between tokens that would normally be separated by a space, and the compiler treats it as a space:
if err /* network timeout? */ != nil {
return fmt.Errorf("request failed: %w", err)
}
The compiler sees if err != nil { … } — as if the comment were simply a space. The expression is valid.
Now consider a block comment that spans multiple lines:
x := 1
/*
This comment contains newlines,
so it acts as a newline between tokens.
*/
y := 2
Because the comment contains newline characters, the compiler treats it as a newline. This has the same effect as a blank line separating the two statements; it does not break the program, but it does separate tokens. The practical upshot: a multi-line block comment never joins two tokens on the same line; it always forces a break.
Cannot split a numeric literal with a comment:
You cannot place any comment — line or general — between the digits of a numeric literal. For example, x := 1 /* thousand */ 000 is invalid because the compiler sees x := 1 000, which is two separate tokens and a syntax error. Comments can sit between a literal and an operator, but never inside a literal itself.
Constraints and Common Mistakes
The Go specification explicitly forbids three things related to comments. Each one can cause a compilation error or silently produce behavior different from what you intended.
Comments Cannot Nest
A /* inside another general comment does not start a nested comment; it is just text. The outer comment ends at the first */, and anything after that (including a trailing */ that you intended to close an inner block) becomes visible code, which usually causes a syntax error.
/*
Outer comment
/* This was meant to be a nested comment, but it isn't. */
Code here is outside any comment and will cause an error.
*/
When the compiler hits the first */ (right after “but it isn't.”), the comment ends. The remaining */ on the next line is not part of any comment and produces a parse error.
Nesting attempt compiles with surprises:
If you try to nest comments, the code between the first */ and the second */ is not commented out. It becomes source code that the compiler will attempt to parse. This often results in baffling error messages on lines you thought were commented out.
Comments Cannot Start Inside a Rune or String Literal
Inside a rune literal (single quotes) or a string literal (double quotes or backticks), the // and /* sequences are just part of the literal’s value. They do not begin a comment.
r := '/*' // r is the rune '/', not the start of a comment.
s := "// not a comment"
t := `/* still not a comment */`
This is intuitive, but it’s worth calling out because it follows from the lexer’s behavior: the lexer consumes a string or rune literal as a single token before any comment detection happens inside it.
A Line Comment Cannot Start a General Comment
A line comment already runs to the end of the line. If you write // /* this is not a block comment */, the /* and */ are part of the line comment’s text and have no special meaning. This is consistent with the rule that a comment cannot start inside another comment.
Doc Comments
Go has a built-in documentation system that extracts specially placed comments and turns them into browsable documentation with go doc. These are called doc comments, and they follow a strict convention: a doc comment appears immediately before a package, constant, variable, type, or function declaration, with no blank line in between.
Doc comments are written as line comments (//) or general comments (/* … */). The comment text itself must begin with the name of the element it documents, and it should be a complete sentence.
// Package mathutil provides helpers for common numeric operations.
package mathutil
// Clamp restricts v to the inclusive range [low, high].
// If v is less than low, low is returned.
// If v is greater than high, high is returned.
// Otherwise v is returned unchanged.
func Clamp(v, low, high float64) float64 {
if v < low {
return low
}
if v > high {
return high
}
return v
}
When you run go doc mathutil.Clamp, the tool prints the comment you wrote for Clamp. This is how the entire Go standard library documents its API, and it is the expected way to document any exported identifier.
Good doc comments are self-validating:
A doc comment that starts with the element’s name and reads like a sentence is automatically recognized by go doc and displayed with proper formatting. You can verify your doc comments at any time by running go doc on your package or symbol. If the output looks natural and informative, you’ve formatted it correctly.
Doc Comment Formatting
The go doc tool supports a lightweight formatting syntax within doc comments:
- A blank line starts a new paragraph.
- Indented lines are shown as preformatted text (code blocks).
- URL-like patterns are automatically linked if they begin with
http://orhttps://.
Example:
// JoinPath joins any number of path elements into a single path,
// separating them with an OS-specific separator. Empty elements
// are ignored. The result is cleaned.
//
// Example:
//
// path := JoinPath("a", "b", "c")
// fmt.Println(path) // "a/b/c" on Unix
func JoinPath(elem ...string) string { … }
The indented lines under Example: will appear as a code block in the generated documentation.
Package doc comments use /* */ conventionally:
For the top-level package comment, it is idiomatic to use a block comment (/* … */) directly above the package clause. This visually separates the package overview from the code that follows. However, // is equally valid and commonly seen in smaller packages.
Comment Directives (Magic Comments)
Some comments aren’t just documentation — they are instructions to tools. The Go compiler, the go command, and the cgo tool recognize certain comment patterns as directives. These directives are still comments from the compiler’s perspective (they do not affect the generated code directly), but they influence how the tool processes the source.
The majority of tool directives start with //go: with no space between // and go:. A space changes the meaning: // go:generate is a plain comment ignored by go generate, while //go:generate triggers a code generation command.
//go:generate stringer -type=Status
type Status int
const (
StatusPending Status = iota
StatusActive
StatusComplete
)
Other common directives:
| Directive | Purpose |
|---|---|
//go:build | Build constraints (replaces older // +build syntax). |
//go:embed | Embed files into the binary at compile time. |
//go:generate | Commands to run when go generate is invoked. |
//line | Adjust file name and line number for diagnostics. (Does not start with //go:.) |
//go:noinline | Prevent the compiler from inlining the following function. |
//go:nosplit | Disable stack splitting for a function. |
The //go:build directive, for example, specifies under which conditions a file should be included in the build:
//go:build linux && amd64
package mypkg
This file will only be compiled when targeting Linux on AMD64. If the condition is not met, the file is ignored entirely — as if it were blank.
The space matters:
A directive written as // go:generate (with a space) is a regular comment. The tool that is supposed to act on it will not see it. This is one of the most common mistakes when writing Go directives. The absence of a space is part of the directive’s syntax.
The //line directive is unusual because it can also appear as a block comment for setting column positions:
//line myfile.src:100
var x = /*line myfile.src:105:3*/ 42
This instructs the compiler to report error positions as if they came from myfile.src at the specified line and column, which is useful when Go code is generated from another source.
Directives are a pragmatic reuse of the comment syntax: the language didn’t need a new syntactic construct, and tools that don’t understand a given directive simply see a comment. This keeps the grammar small and the toolchain extensible.
When to Use Each Form
A practical guide for choosing the right comment:
- Explanatory remarks inside a function body: Use
//. They are short, scannable, and the standard for in-line documentation. - Large commented-out blocks of code: Use
/* */but only temporarily. If the block stays, delete it and rely on version control history. A block comment can surround many lines without requiring//on every line, but it cannot nest, so be careful. - Package-level and exported-identifier documentation: Use
//(or/* */for the package overview) and follow the doc comment conventions. This is not optional if you want your package to be usable by others. - Tool directives: Always use
//go:namewith no space. Check the tool’s documentation to confirm the exact spelling.
Self-documenting code is not a substitute:
Go encourages clear naming that reduces the need for comments. Still, even well-named functions deserve doc comments that explain behavior, edge cases, and guarantees. Comments and good names work together; they do not compete.
Summary
Comments in Go are a small surface area with precise rules. The line comment // is the everyday workhorse. The general comment /* */ allows multi-line blocks and, when it contains no newline, can slip between tokens like a space — a detail that few languages expose so explicitly. Doc comments turn that same syntax into first-class API documentation, and directive comments extend the syntax into tool configuration without adding new grammar to the language.
The single most important detail to remember: a comment cannot start inside another comment, and directive prefixes require exact, space-free spelling. Getting those wrong produces errors or silently ignored instructions.