Identifiers and Keywords
Learn how Go identifiers name program entities, the rules for valid names, exported vs unexported identifiers, the 25 reserved keywords, and predeclared identifiers.
Every name you give to a variable, function, type, or package in Go is an identifier. The language enforces precise rules for what can be an identifier and which words are off‑limits. Understanding these rules is not just about avoiding compiler errors; it shapes how your code is read, how packages expose their public surface, and how you reason about scope.
What Identifiers Are
An identifier is a sequence of one or more letters and digits. The first character must be a letter, where “letter” means any Unicode letter or the underscore _. Digits are the Unicode category “Number, decimal digit” (Nd), which covers 0–9 across many scripts.
From the Go specification, the formal definition is:
identifier = letter { letter | unicode_digit } .
letter itself is any Unicode letter (categories Lu, Ll, Lt, Lm, Lo) or the underscore. That wide definition means you can use characters beyond ASCII, but the rules around exported names and readability will guide real‑world choices.
The Naming Rules in Detail
Every identifier you write must obey these constraints:
- It must start with a letter or an underscore.
- After the first character, it may contain any mix of letters, underscores, and Unicode digits.
- It is case‑sensitive:
value,Value, andVALUEare three distinct names. - It cannot be any of Go’s 25 reserved keywords.
- There is no length limit, though idiomatic Go favors short, clear names that respect their scope.
What Makes an Identifier Valid
// perfectly valid identifiers
userCount
_ // blank identifier
π // Greek small letter pi
_privateVar
Ĝo // Ĝ is a Unicode uppercase letter (Lu)
данные // Cyrillic word for "data"
エラー // Japanese "error"
A digit cannot be the first character, so 9lives is invalid, as is any name that starts with a number. You also cannot use a keyword like func or type as a name, no matter how tempting it seems.
Compile Error:
Using a reserved keyword as an identifier produces a compile‑time error. There is no escape mechanism—the compiler treats it as a keyword unconditionally.
Exported vs. Unexported Identifiers
Go’s visibility model is built entirely on the first character of the identifier. If that character is an uppercase Unicode letter (the Lu category), the name is exported—it becomes part of the package’s public API. Otherwise, the name is unexported (private to the package).
package geometry
// Exported: visible to importing packages.
func Area(r float64) float64 { /* ... */ }
// Unexported: only accessible within the geometry package.
func internalFormat() string { /* ... */ }
The rule works across Unicode: Π (Greek capital Pi, U+03A0) is an uppercase letter, so a name like Π is exported. However, many scripts do not have a case distinction; characters from such scripts are treated as lowercase, making the identifier unexported. This is why eastern CJK characters, for instance, never produce exported names on their own.
Encapsulation by Convention:
When your package exposes only exported names as its public surface and keeps implementation details behind unexported names, you are following the core Go idiom of encapsulation without extra keywords.
The Blank Identifier
The underscore _ is a predefined identifier called the blank identifier. It behaves like a write‑only placeholder:
- You can declare it, assign to it, or use it in multiple‑variable assignments any number of times.
- You cannot read its value—any attempt to reference
_results in a compile‑time error.
It serves as an explicit signal that a value is deliberately being ignored.
// Discard the error return value explicitly.
data, _ := ioutil.ReadFile("config.json")
// Use blank import to trigger a package's init() function.
import _ "net/http/pprof"
Note:
Using _ to ignore values is not just about silencing “unused variable” errors. It documents to future readers that the omission is intentional, not a mistake.
Predeclared Identifiers
Beyond keywords, Go declares several identifiers in the universe block—the outermost scope that encloses all source code. These names are not reserved; you can redeclare them in a smaller scope, but doing so shadows the original.
Common predeclared identifiers include:
| Category | Names |
|---|---|
| Types | bool, byte, complex64, complex128, error, float32, float64, int, int8, int16, int32, int64, rune, string, uint, uint8, uint16, uint32, uint64, uintptr |
| Constants | true, false, iota |
| Zero value | nil |
| Functions | append, cap, close, complex, copy, delete, imag, len, make, new, panic, print, println, real, recover |
Because these are just ordinary identifiers, code like var string = "hello" compiles perfectly. It declares a new variable named string in the local scope, hiding the type.
Shadowing Predeclared Names:
Shadowing a predeclared identifier compiles without complaint, but it makes the original unreachable within that scope. Re‑declaring len or string can create subtle, hard‑to‑diagnose bugs. Avoid using any predeclared name for your own variables or types, unless you have a clear, documented reason.
The 25 Reserved Keywords
Go keeps its vocabulary deliberately small: only 25 keywords are reserved, and none can be used as an identifier. They are case‑sensitive—For is a valid identifier, while for is the loop keyword.
Here they are, grouped by their role in the language:
Declarations
const, func, import, package, type, var
Composite type literals
chan, interface, map, struct
Control flow
break, case, continue, default, else, fallthrough, for, goto, if, range, return, select, switch
Concurrency & defer
defer, go
The absence of certain keywords is notable. Go has no while—you use for with only a condition. No class—you use struct with methods. No try/catch—errors are values. This minimal keyword set is a deliberate design choice that keeps the grammar simple and the compiler fast.
// The only way to loop is for—no separate while keyword.
for i := 0; i < 10; i++ {
// ...
}
// Condition-only for acts like a while loop.
for condition {
// ...
}
Note:
Because there are only 25 keywords, you rarely have to worry about accidentally colliding with them. If you come from languages with dozens of reserved words, this is one of the first pleasant surprises in Go.
Common Misconceptions and Pitfalls
Assuming Any Non‑ASCII Letter Is Unexported
Not every non‑ASCII letter is treated as lowercase. The Greek capital Π is uppercase and therefore exported. The Russian Я (Cyrillic capital Ya) is uppercase and exported. A name like Яблоко would be exported. The only reliable test is whether the first character belongs to the Unicode Lu category.
Treating Predeclared Identifiers as Reserved
It is common to think of names like len, string, or error as reserved. They are not. The compiler will not stop you from shadowing them, but the resulting confusion in a team codebase can be substantial. Use different names.
Overusing the Blank Identifier to Ignore Errors
Discarding an error return with _ hides a failure that might matter. The blank identifier is a tool for cases where you genuinely do not care about a value, not a way to skip error handling. In production code, always check errors explicitly.
// Dangerous: silently ignoring a potential failure.
data, _ := ioutil.ReadFile("config.json")
// Safer: handle the error even if the file is optional.
data, err := ioutil.ReadFile("config.json")
if err != nil {
if os.IsNotExist(err) {
data = defaultConfig()
} else {
log.Fatal(err)
}
}
Using Reserved Keywords as Identifiers
Even with the small keyword set, a beginner might naturally write var type string or func select(). The compiler immediately rejects this. The fix is always to choose a different, more specific name (var kind string or func pickOne()).
Summary
Identifiers in Go are built from a generous set of Unicode letters and digits, governed by a few simple rules: start with a letter or underscore, avoid keywords, and let the first character control visibility. The 25 reserved keywords form the core syntax; everything else you create is an identifier, including the predeclared names that sit in the universe block, ready to be shadowed if you are not careful.