The const keyword declares a name for a value the compiler knows at build time, such as a timeout, a header name or a size limit. A Go constant can only hold a boolean, a number, a string or a rune, and it can never change while the program runs. Most constants are untyped, which means they have no fixed type until you use them, so const maxBodyBytes = 1 << 20 works as an int, an int64 or a float64 without a conversion. Inside a const block, the predeclared identifier iota counts up from 0, and that is how Go writes enums (Go spec).
TL;DR
const name = valuedeclares one constant. Aconst ( ... )block declares several, and most Go code groups related constants that way.- Constants can be booleans, numbers, strings or runes. Slices, maps, structs and the result of a function call like
time.Now()can't be constants. - An untyped constant like
5or"GET"takes the type its context needs.5 * time.Secondcompiles for that reason, whiletimeout * time.Secondwith anintvariable does not. - Untyped constants are exact. The compiler does their arithmetic with arbitrary precision and only complains when a value doesn't fit the type it ends up in.
iotastarts at 0 in everyconstblock and goes up by one per line._skips a value,iota + 1starts at 1 and1 << iotabuilds bit flags.- Go has no
enumkeyword. An enum is a named type, aconstblock withiotaand usually aString()method, which thestringertool can generate. - You can't take the address of a constant or assign to one. Values computed at runtime, and lookup tables like maps, belong in a
var.
How do you declare a constant in Go?
Write const, a name and a value. For several related constants, use a grouped block with parentheses:
example.gogopackage main import ( "fmt" "net/http" "time" ) const maxBodyBytes = 1 << 20 // 1 MiB const ( readTimeout = 5 * time.Second writeTimeout = 10 * time.Second idleTimeout = 2 * time.Minute ) const ( headerRequestID = "X-Request-ID" headerRetryAfter = "Retry-After" ) func main() { srv := &http.Server{ Addr: ":8080", ReadTimeout: readTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, } fmt.Println(srv.ReadTimeout, srv.WriteTimeout, srv.IdleTimeout) }
This prints 5s 10s 2m0s. For plain values like these, the grouping is only for readers. Each name in the block is an independent constant, and the block keeps the server's timeouts in one place where a reviewer can compare them.
Constants follow the same scope and visibility rules as variables. A constant declared at package level is visible in every file of the package, and it is exported if its name starts with a capital letter, as in http.StatusNotFound or time.RFC3339. The Go package keyword covers how exported names work across packages. A constant declared inside a function only exists in that function. Go style uses mixed caps for constants like any other name, so the idiomatic spelling is maxBodyBytes or MaxBodyBytes, not MAX_BODY_BYTES.
A const block has one rule that a var block doesn't. When a line has a name but no type and no value, it repeats the type and expression of the line before it. On its own that only gives several names the same value, which is rarely what you want. Combined with iota, it lets one expression number a whole list of constants.
What values can be constants in Go?
Only booleans, numbers, strings and runes. The number kinds are integers, floating-point and complex numbers, and a rune is an integer that stands for a Unicode code point. Anything else fails to compile, including a value that is fixed in practice but has to be computed at runtime:
example.gogopackage main import "time" const startedAt = time.Now()
example.texttext./main.go:5:19: time.Now() (value of struct type time.Time) is not constant
Composite values are rejected the same way. const allowedMethods = []string{"GET", "POST"} fails with []string{…} (value of type []string) is not constant. Slices, maps, structs, pointers, channels and functions can't be constants, even when every element is a literal.
Named types built on a number, string or boolean are allowed. time.Duration is defined as an int64, so const pollInterval = 500 * time.Millisecond is a constant of type time.Duration with the value 500ms. The same goes for your own types, like type LogLevel int, which is what enums build on.
A few built-in functions also produce constants when their argument allows it. len of an array is a constant because an array's length is part of its type. len("Retry-After") on a string constant is a constant too. unsafe.Sizeof, unsafe.Alignof and unsafe.Offsetof are constants whenever the argument's type has a fixed size, so const headerSize = unsafe.Sizeof(int64(0)) compiles and has the value 8. len of a slice is not a constant, since a slice's length is only known at runtime.
What is the difference between typed and untyped constants in Go?
A typed constant has a type in its declaration. An untyped constant doesn't, and it takes whatever type the code around it needs:
example.gogoconst maxRetries int32 = 3 // typed: always an int32 const maxBodyBytes = 1 << 20 // untyped: becomes whatever type it is used as
The typed one follows the normal assignment rules, so it can't go into an int without a conversion:
example.gogovar attempts int = maxRetries
example.texttext./main.go:6:21: cannot use maxRetries (constant 3 of type int32) as int value in variable declaration
The untyped one fits into var limit int64 = maxBodyBytes, var limit int = maxBodyBytes and float64(maxBodyBytes) alike. The standard library relies on that flexibility and leaves most of its numeric constants untyped, like math.MaxInt64 and math.Pi.
An untyped constant still has a default type. It is used when nothing else decides the type, as in x := 1.5 or when the constant is passed to fmt.Println:
| Untyped constant | Default type |
|---|---|
true, false | bool |
42, 1 << 20 | int |
1.5, 1e9 | float64 |
'a' | rune (an alias for int32) |
2i | complex128 |
"GET" | string |
Why does 5 * time.Second compile but timeout * time.Second fails?
Because 5 is an untyped constant and timeout is a variable. Go never converts between numeric types on its own, but an untyped constant can take any numeric type its value fits in. In 5 * time.Second, the 5 becomes a time.Duration, and the result is a time.Duration of five seconds.
A variable already has a type, so the same multiplication fails when the number comes from configuration:
example.gogopackage main import ( "net/http" "os" "strconv" "time" ) func main() { timeout, _ := strconv.Atoi(os.Getenv("TIMEOUT_SECONDS")) client := &http.Client{Timeout: timeout * time.Second} _ = client }
example.texttext./main.go:12:34: invalid operation: timeout * time.Second (mismatched types int and "time".Duration)
timeout is an int, and time.Second is a time.Duration. The fix is an explicit conversion, time.Duration(timeout) * time.Second. An untyped constant with the same value works without one, so const retryDelay = 3 followed by retryDelay * time.Second gives 3s.
How precise are untyped constants?
Untyped numeric constants are exact. The spec requires compilers to represent integer constants with at least 256 bits and to keep floating-point constants accurate to a mantissa of at least 256 bits, and the standard Go compiler allows up to 512 bits. math.Pi is written out with more digits than a float64 can hold, and it is only rounded when it lands in a float64 or float32 variable.
That precision lets you write intermediate values that are too big for any Go type, as long as the final result fits. const maxOffset = 1 << 100 compiles, and maxOffset >> 98 is the constant 4. The error only comes when the value has to become a concrete type:
example.gogopackage main import "fmt" const maxOffset = 1 << 100 func main() { fmt.Println(maxOffset) }
example.texttext./main.go:8:14: cannot use maxOffset (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)
Because the compiler knows every constant value, these range checks happen at build time instead of wrapping around silently at runtime. A few more examples:
| Code | Compiler error |
|---|---|
var uploadLimit int32 = 1 << 40 | cannot use 1 << 40 (untyped int constant 1099511627776) as int32 value in variable declaration (overflows) |
const maxConns uint8 = 300 | cannot use 300 (untyped int constant) as uint8 value in constant declaration (overflows) |
var workers int = 2.5 | cannot use 2.5 (untyped float constant) as int value in variable declaration (truncated) |
The check looks only at the value. var workers int = 2.0 compiles, because 2.0 is an untyped float constant whose value is a whole number.
What is iota in Go?
iota is a predeclared identifier that only means something inside a const declaration. It is 0 for the first constant spec in a const block and goes up by one with each spec after it. A spec is one line that declares names, so blank lines and comments don't count. Combined with the implicit repetition of the previous expression, a type and one iota are enough to number a whole list:
example.gogotype LogLevel int const ( LevelDebug LogLevel = iota // 0 LevelInfo // 1 LevelWarn // 2 LevelError // 3 )
LevelInfo, LevelWarn and LevelError have no type or value of their own, so each one repeats LogLevel = iota with the next value of iota. Every constant in the list has the type LogLevel.
How iota counts:
iotaresets to 0 at the start of everyconstblock. Two separate blocks both start at 0.- It counts specs, not names. Two constants declared on the same line see the same
iota. - A line that uses
_still counts, so the blank identifier skips a value. iotais an untyped integer constant, so it works in any constant expression, likeiota + 1or1 << iota.
How do you start iota at 1 or skip a value?
Add one to it, or throw the first value away with _. Starting at 1 keeps the zero value free to mean "not set":
example.gogotype Priority int const ( PriorityLow Priority = iota + 1 // 1 PriorityNormal // 2 PriorityHigh // 3 )
Skipping with _ fits cases where the first value is meaningless. The size units below skip iota == 0, since 1 << 0 would be 1 byte:
example.gogotype ByteSize int64 const ( _ = iota // skip 0 KB ByteSize = 1 << (10 * iota) MB GB )
KB is 1 << 10, which is 1024, MB is 1 << 20 and GB is 1 << 30. Each line repeats ByteSize = 1 << (10 * iota) with a larger iota.
How do you define bit flags with iota?
Shift 1 left by iota. Each constant then gets its own bit, and you combine them with |:
example.gogopackage main import "fmt" type Permission uint8 const ( PermRead Permission = 1 << iota // 1 PermWrite // 2 PermDelete // 4 ) func main() { perm := PermRead | PermWrite fmt.Println(perm&PermWrite != 0) // true fmt.Println(perm&PermDelete != 0) // false }
The standard library uses the same pattern. log.Ldate, log.Ltime and log.Lshortfile are 1 << iota flags that you combine in log.SetFlags(log.LstdFlags | log.Lshortfile).
Does Go have enums?
Not as a separate language feature. There is no enum keyword. Go builds enums from three parts it already has: a named type, a const block with iota and a String() method so the values print as names instead of numbers:
example.gogopackage main import "fmt" type LogLevel int const ( LevelDebug LogLevel = iota LevelInfo LevelWarn LevelError ) func (l LogLevel) String() string { switch l { case LevelDebug: return "debug" case LevelInfo: return "info" case LevelWarn: return "warn" case LevelError: return "error" default: return fmt.Sprintf("LogLevel(%d)", int(l)) } } func main() { fmt.Println(LevelWarn, LogLevel(9)) fmt.Printf("level=%v\n", LevelError) }
This prints warn LogLevel(9) and then level=error. fmt calls String() when it prints a value with Println, %v or %s. The default case turns a value outside the list, like LogLevel(9), into something readable.
How do you generate String methods with stringer?
Writing that switch by hand gets tedious, and it goes stale when someone adds a constant and forgets the method. The stringer tool from the Go team generates it. Add a go:generate directive next to the type and run go generate:
example.gogopackage main import "fmt" //go:generate go run golang.org/x/tools/cmd/stringer@latest -type=OrderStatus -trimprefix=OrderStatus type OrderStatus int const ( OrderStatusUnknown OrderStatus = iota OrderStatusPending OrderStatusPaid OrderStatusShipped OrderStatusRefunded ) func main() { fmt.Println(OrderStatusPaid, OrderStatus(42)) }
go generate writes orderstatus_string.go with a String() method, and the program prints Paid OrderStatus(42). -trimprefix drops the shared prefix from the names. The generated file also contains a small compile-time check. When an existing constant's value changes and the file hasn't been regenerated, the build fails with an error like invalid argument: index 4 out of bounds [0:1]. A constant added at the end of the list is not caught, and it prints as OrderStatus(5) until you run go generate again, so many teams run it in CI and fail the build when the generated files change.
Why should an enum's zero value be Unknown?
Because every Go variable starts at its zero value, and for an int-based enum that is 0. An Order struct decoded from JSON without a status field, or declared with var o Order, has status 0. If 0 is OrderStatusPending, a missing value looks like a real pending order. Putting an explicit OrderStatusUnknown first, or starting at iota + 1, makes the zero value mean "not set" so the code can reject it.
Does Go check that a switch covers every enum value?
No. An enum type is still an ordinary integer type, so the compiler doesn't know which values are valid. var level LogLevel = 9 compiles, since 9 is an untyped constant that fits in an int. A switch that handles only some of the values also compiles without a warning.
Teams that want that check add the exhaustive linter, which golangci-lint includes. It reports a switch on an enum type that misses one of the type's constants. A default case, like the one in the String() method above, handles values outside the list at runtime.
When should you use const vs var in Go?
Use const for values that are known when you write the code and never change: timeouts, limits, header names, enum values. Use var for anything computed at runtime, read from configuration or built from a composite type.
You can't take the address of a constant, since a constant isn't stored in a variable:
example.gogop := &maxBodyBytes
example.texttext./main.go:6:8: invalid operation: cannot take address of maxBodyBytes (untyped int constant 1048576)
This comes up with APIs that take pointers to optional fields. Since Go 1.26, new accepts an expression, so new(maxBodyBytes) returns an *int pointing to a copy of the value. On older versions, copy the constant into a variable first and take its address.
You can't assign to a constant either. maxBodyBytes = 2 << 20 fails with cannot assign to maxBodyBytes (neither addressable nor a map index expression).
Unused constants compile. An unused local variable is an error, declared and not used: retries, but an unused constant, even inside a function, is accepted. Linters like staticcheck still report unused unexported constants at package level.
Lookup tables are variables. A map from status to label can't be a constant, so declare it with var at package level, or return it from a function when you don't want other code to modify it:
example.gogovar orderStatusLabels = map[OrderStatus]string{ OrderStatusPending: "Awaiting payment", OrderStatusPaid: "Paid", OrderStatusShipped: "On the way", OrderStatusRefunded: "Refunded", }
A package-level var can be changed by any code in the package, which is the trade-off for being able to hold any type. When the values are fixed and simple, a constant gives you compile-time checks and no risk of changes at runtime. The Go var keyword covers variables, zero values and := in depth.
Where LevelUpGo fits
LevelUpGo teaches Go through exercises that run real Go code in the browser. Go Basics introduces const alongside variables and types. Go Language Deep Dives has a lesson on iota and const patterns where you build an enum, bit flags and byte size units. The Training Ground has short standalone exercises for practicing outside a course. For the other 24 reserved words, see Go keywords: all 25 explained.
FAQ
Is const a keyword in Go?
Yes. const is one of Go's 25 reserved keywords, so you can't use it as a variable, function or type name. It starts a constant declaration, either a single const name = value or a grouped const ( ... ) block.
Can a slice or map be a constant in Go?
No. Constants can only be booleans, numbers, strings and runes, or named types built on them. const allowedMethods = []string{"GET", "POST"} fails with is not constant. Use a package-level var for slices and maps, or a function that returns a fresh copy when callers must not modify the shared one.
What is iota in Go?
iota is a predeclared identifier that numbers the lines of a const block. It is 0 on the first line, goes up by one on each following line and resets in every new block. With a named type and implicit repetition, it gives each constant in a list its own value, which is how Go writes enums and bit flags.
What is the difference between typed and untyped constants in Go?
A typed constant, like const maxRetries int32 = 3, has a fixed type and follows the normal assignment rules. An untyped constant, like const maxRetries = 3, has no type until it is used, so it can become an int, an int64, a float64 or a time.Duration. Untyped constants also keep exact, arbitrary-precision values until they are converted.
Does Go have enums?
Go has no enum keyword, but it has enums in practice. You declare a named type such as type OrderStatus int, list its values in a const block with iota and add a String() method, often generated by stringer. The compiler doesn't restrict the type to the listed values, so validate input and use the exhaustive linter if you need every switch to cover every value.
Sources
- The Go Programming Language Specification, Constants: https://go.dev/ref/spec#Constants
- The Go Programming Language Specification, Constant declarations: https://go.dev/ref/spec#Constant_declarations
- The Go Programming Language Specification, Iota: https://go.dev/ref/spec#Iota
- The Go Programming Language Specification, Representability: https://go.dev/ref/spec#Representability
- The Go Blog, Constants (Rob Pike): https://go.dev/blog/constants
- Effective Go, Constants: https://go.dev/doc/effective_go#constants
- stringer: https://pkg.go.dev/golang.org/x/tools/cmd/stringer
- Go Code Review Comments, Mixed caps: https://go.dev/wiki/CodeReviewComments#mixed-caps
- exhaustive linter: https://github.com/nishanths/exhaustive
