How to Write Comments in Go

Single-Line and Multi-Line

You copy a function into a new file, add a few lines of explanation, run `go doc`, and see nothing. You switch to the block comment syntax you learned in another language, run it again, and still get silence. The code works perfectly. The tooling just ignores your notes.

The comment that actually matters

You paste a function into a new file, add a few lines of explanation above it, and run go doc. The terminal returns an empty signature. You switch to the block comment syntax you used in Python or JavaScript, run it again, and get the same silence. The code compiles. The logic works. The tooling just ignores your notes.

Go treats comments as architectural blueprints, not scratchpads. The language inherited its syntax from C, which means it supports both // and /* */. The ecosystem, however, only reads one of them. Understanding which one matters saves you from writing documentation that vanishes from generated references, IDE tooltips, and package indexes.

Comments in Go serve two distinct roles. The single-line style is the blueprint that gets published. The block style is the sticky note you use to cover a crack in the wall while you figure out how to fix it. The Go toolchain only indexes the blueprints.

How Go reads your notes

The compiler strips comments before it parses your code. They do not affect compilation speed, binary size, or runtime behavior. The Go toolchain runs a separate documentation extractor that scans the source tree for // comments attached to top-level declarations. This extractor powers go doc, gopls, and every major IDE's hover tooltips.

The extractor follows strict rules. A doc comment must sit immediately before a declaration. No blank lines are allowed between the comment and the func, type, var, or const keyword. The first sentence must start with the name of the declared entity and end with a period. This structure lets the tooling parse the comment into a title and a body automatically.

Here is the simplest valid doc comment:

package main

// Add returns the sum of two integers.
// It panics if the result overflows.
func Add(a, b int) int {
	// Check bounds before adding to avoid silent wraparound
	if a > 0 && b > 2147483647-a {
		panic("overflow")
	}
	return a + b
}

The documentation extractor reads the first line as the title. It treats everything after the first period as the body. IDEs use this exact split to show a bold heading and a description when you hover over Add. The inline // comment inside the function is ignored by the doc extractor. It stays in the source file for human readers, and gofmt preserves it exactly where you placed it.

Doc comments are public-facing. Inline comments are private. Treat them accordingly.

The doc comment contract

Writing a doc comment in Go follows a convention that feels rigid until you see how it scales across a codebase. The first word must match the declared name. The first sentence must be a complete statement ending with a period. Subsequent lines can explain edge cases, performance characteristics, or usage examples.

This convention exists because go doc strips the leading name when displaying the output. If you write // Add returns the sum..., the tool prints returns the sum... as the heading. If you write // Calculates the sum..., the tool prints calculates the sum... and breaks the expected capitalization. The rule keeps generated documentation consistent across thousands of packages.

Exported names require doc comments. Unexported names do not. The Go community expects every capital-letter function, type, or variable to have a doc comment. Private helpers can skip it, but adding one is still encouraged when the logic is non-obvious. This convention aligns with the language's visibility model: if you expose it, you document it.

Here is a realistic package setup showing the contract in action:

package calculator

// MaxValue holds the upper limit for safe arithmetic operations.
// Values above this threshold trigger an overflow check.
const MaxValue = 1000

// Result stores the output of a calculation.
// It includes the raw value and a success flag.
type Result struct {
	Value   int
	Success bool
}

// Multiply computes the product of two integers.
// It returns a Result with Success set to false if either input is negative.
func Multiply(x, y int) Result {
	// Validate inputs before performing the operation
	if x < 0 || y < 0 {
		return Result{Success: false}
	}
	return Result{Value: x * y, Success: true}
}

The extractor attaches the first comment to MaxValue, the second to Result, and the third to Multiply. Running go doc calculator prints a clean, structured reference. Running go doc calculator.Multiply prints just the function's documentation. The tooling works because the comments follow the contract.

Break the contract and the documentation fractures. Follow it and your package becomes self-documenting.

When block comments earn their keep

The /* */ syntax exists for two specific purposes. The first is disabling large sections of code during debugging. Wrapping a function body or a loop in a block comment lets you skip compilation without deleting lines. The second is placing license headers or generated-file warnings at the top of a file.

Block comments do not integrate with go doc. They do not appear in IDE tooltips. They are invisible to the documentation extractor. This invisibility is a feature, not a bug. It keeps generated references clean while giving developers a reliable way to temporarily mute code or attach legal text.

Here is how a license header and a debug block look in practice:

/*
Copyright 2024 Example Corp.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
*/

package main

// Process handles incoming requests.
// It validates headers and routes to the correct handler.
func Process() {
	/*
	// Temporary debug block
	// fmt.Println("tracing request")
	// time.Sleep(2 * time.Second)
	*/
	// Route to the appropriate handler
	// handler.ServeHTTP(w, r)
}

The block comment at the top stays in the file permanently. The block comment inside Process is meant to be removed before merging. Leaving debug blocks in production code creates dead weight and confuses future readers. Delete them when the fix ships.

Block comments are for muting code and attaching headers. Never use them for documentation.

What the compiler does with your words

The Go compiler treats comments as whitespace during parsing. They are stripped before the AST is built. This means comments cannot affect control flow, type checking, or optimization. You cannot hide a semicolon inside a comment and expect the compiler to find it. You cannot use a comment to split a long identifier across lines.

If you accidentally comment out a closing brace or a required keyword, the compiler rejects the program with expected '}', found 'EOF'. The error points to the line where the parser lost track of the block structure. The compiler does not care that you intended to comment out code. It only sees the remaining tokens.

The gofmt tool preserves comments exactly as written. It does not reflow them, fix their capitalization, or attach them to declarations. If you place a blank line between a comment and a function, gofmt keeps the blank line. The doc extractor then ignores the comment. Formatting tools and documentation tools operate independently. You are responsible for keeping them aligned.

Comments are invisible to the compiler but visible to your team. Write them with that audience in mind.

Pitfalls and silent failures

The most common mistake is breaking the doc comment contract with a single blank line. A blank line between the comment and the declaration severs the attachment. The extractor treats the comment as a standalone note and drops it from the generated docs. The code compiles fine. The documentation vanishes.

Another frequent issue is using /* */ for function descriptions. Developers coming from C or Java often wrap multi-line explanations in block comments. Go's tooling ignores them. The function appears in go doc with no description. IDE tooltips show only the signature. The package feels undocumented even though you wrote paragraphs of explanation.

Commenting out large blocks and forgetting to remove them creates technical debt. Dead code accumulates, review times increase, and new contributors waste time deciphering abandoned logic. The compiler does not warn you about commented-out code. It does not track how long a block has been muted. You must clean it up manually.

Inline comments that repeat the code provide zero value. Writing // increment counter above count++ adds noise. Inline comments should explain why a decision was made, not what the syntax does. They should cover edge cases, performance tradeoffs, or external constraints.

The worst comment is the one that lies. Update comments when the code changes. Outdated documentation is worse than no documentation.

Choosing your comment style

Use // for all documentation attached to exported declarations. Use // for inline explanations that clarify non-obvious logic. Use // for package-level overviews that describe the module's purpose. Use /* */ for license headers and generated-file warnings at the top of a file. Use /* */ for temporarily disabling code during debugging, and delete it before merging. Use plain sequential code when the logic is self-evident: the simplest thing that works is usually the right thing.

Where to go next