Constant Declarations
How to declare constants in Go - the const keyword, grouped constants, and the crucial difference between typed and untyped constants
A constant in Go is a named value that never changes. Once you define a constant, the compiler guarantees nothing in your program can alter it. This is different from a variable, where the value can be updated whenever you like. Constants exist only at compile time — they don't occupy memory at runtime the way variables do. The compiler can inline them, use them in more flexible type situations, and reject any code that tries to modify them.
Go supports four kinds of constant values: booleans, strings, characters (runes), and numbers. Everything on this page applies to all of them.
Declaring Constants with the const Keyword
The syntax for a single constant mirrors variable declarations, but with const instead of var.
const greeting = "Hello, Gopher"
const maxRetries = 3
const piApprox = 3.14
const debugMode = false
Each of these constants is untyped — the type has not been set yet. The Go compiler sees 3 as an abstract integer and 3.14 as an abstract float; it doesn't nail down a specific concrete type like int or float64 until it's forced to. You can use these constants with any compatible type without explicit conversion, which is one of the main reasons Go constants feel lightweight compared to variables.
To give a constant a specific type, you write the type after the constant name:
const typedGreeting string = "Hello, Gopher"
const maxAttempts int = 5
Now typedGreeting is a string and maxAttempts is an int. These are typed constants. A typed constant behaves like a regular variable of that type: you cannot assign it to a different type without an explicit conversion, and you cannot use it in expressions that mix incompatible types.
Constant immutability:
You cannot reassign a constant after its declaration. Attempting maxRetries = 5 later in the code is a compile-time error: "cannot assign to maxRetries". Constants are immutable.
Constants can appear at the package level or inside functions, just like variables. Scoping rules are identical — a constant declared inside a function is only visible within that function.
What Can Be a Constant?
Only values the compiler can figure out at compile time are allowed. Literals like 42, "hello", true, and 3.14 + 2i are fine. The result of a function call is not:
import "math"
const a = math.Sqrt(16) // compile error: math.Sqrt(16) is not a constant
The function math.Sqrt runs at runtime, not compile time, so the compiler can't evaluate it. Variables are also runtime-only and cannot be used to initialize a constant:
var x = 10
const c = x // compile error: x is not constant
The same rule applies to anything that requires runtime computation, like len on a slice (but len on a string or array constant works because the length is known at compile time).
Runtime expressions are forbidden:
If you see "… is not constant" in an error, the expression you wrote cannot be evaluated at compile time. Replace it with a literal or a constant expression involving only other constants.
Constants also cannot be declared with the := short declaration syntax. := is only for variables:
const speed := 100 // syntax error
You must always use const speed = 100 or const speed int = 100.
Typed vs Untyped Constants
One of the most important ideas in Go's constant system is the distinction between typed and untyped constants. It's what makes math.Sqrt(2) compile without a type cast, even though math.Sqrt expects a float64 and 2 looks like an integer.
An untyped constant has no fixed Go type. It's just a value — an abstract integer, an abstract float, a boolean, or a string. Because it has no type, it can be used with any type that can represent that value without causing a type mismatch. That's why you can write:
const x = 42
var i int = x
var f float64 = x
var c complex128 = x
The constant 42 is compatible with all of those types. The compiler sees that each variable expects a specific type, and the untyped constant fits; no conversion syntax is required.
A typed constant, on the other hand, has a concrete Go type set at declaration:
const x int = 42
var i int = x // OK, same type
var f float64 = x // compile error: cannot use x (type int) as type float64
Now x is int, so you can't assign it to float64 without an explicit conversion: float64(x).
This split is not theoretical. It's the mechanism that lets Go be both type-safe and convenient with constants. You get strict type checking between variables, but constants can flow across type boundaries as long as the value is representable.
What untyped constants give you:
If your constant doesn't need a specific type right away, leave it untyped. You can always assign it to a typed variable later. This avoids littering your code with casts and makes refactoring easier.
Default Types
Every untyped constant has an implicit default type that kicks in when the compiler needs to give it a concrete type — for example, when you use := to infer a variable's type from the constant, or when you pass it directly to fmt.Printf without an explicit target type.
- Integer literals default to
int. - Floating-point literals default to
float64. - Complex literals default to
complex128. - Rune literals default to
rune(which isint32). - String literals default to
string. - Boolean literals default to
bool.
So x := 10 makes x an int, and name := "Gopher" makes name a string. The constant supplies the type.
In contexts where a typed constant is required (like a function argument with a concrete type), the untyped constant must be representable in that type. If it isn't, the compiler complains:
const huge = 1_000_000_000_000_000_000_000 // too large for int64
var i int64 = huge // compile error: constant overflows int64
The constant itself has infinite precision in the compiler's internal representation, but the target type imposes a limit.
Overflow happens silently during assignment:
The constant itself doesn't overflow — the value is stored with arbitrary precision at compile time. The overflow error only appears when you try to squeeze it into a typed variable that can't hold it.
Grouped Constants
You can declare several constants together in a parenthesized block. This groups related values, making the code more readable and easier to maintain.
const (
httpMethod = "GET"
maxRetries = 4
timeout = 30 // seconds
)
Each line declares a new constant, and you can mix typed and untyped declarations inside the same block. The grouping is purely organizational; it doesn't change the semantics of the constants.
Grouped constants are especially useful when combined with iota (covered in the next section), but they're perfectly valid without it. Use them whenever you have multiple related constants that belong together — configuration values, error codes, or flags.
A common pattern is to define enums using consecutive integer constants:
const (
StatusActive = 1
StatusInactive = 2
StatusBanned = 3
)
This works, but iota makes it much cleaner. We'll explore that in detail under the iota section.
Grouping for logical cohesion:
Group constants by purpose, not by type. Keeping all database-related constants together improves discoverability and signals intent better than scattering them across the file.
Constants in a group can reference each other, as long as the references are valid compile-time expressions. For instance:
const (
base = 100
double = base * 2
half = base / 2
)
This is perfectly legal because all values are known at compile time.
Common Mistakes
Using := for constants. The short variable declaration is for variables only. Constants always use const name = value.
Expecting a constant to be a variable. If a value needs to change during execution, use var. Marking something const communicates "this will never change" — don't subvert that promise by trying to assign it later.
Confusing typed and untyped constants when sharing code. If you export a typed constant from a package, consumers are locked into that type. An untyped constant is more flexible and usually preferred unless you need to enforce a specific type for some reason.
Assuming all constants have the same default type. The default type follows the literal syntax, not the intended usage. The constant 5 defaults to int, not int64 or uint, even if you plan to use it with an int64 field. That's fine until you pass it to a function that expects int64 and the constant's default type inference chooses int — then you'll get a type mismatch. Solution: either declare the constant with the exact type you need, or let it remain untyped and assign it to a typed variable before passing.
Accidental type lock-in:
Typed constants are less flexible than untyped ones. Don't add a type to a constant unless you need to restrict its use or enforce a specific type for correctness.
Summary
You've seen that constants in Go are immutable compile-time values declared with const. They can be untyped, which lets you use them with any compatible type without casts, or typed, which locks them to a single Go type. Grouped constant blocks help organize related values, and the distinction between typed and untyped constants is fundamental to writing clean, type-safe Go.