>_
GolangStepByStep
Intern

Constants

Typed and untyped constants, iota, and why immutability matters

# What is a Constant?

A constant is a value that is fixed at compile time and cannot be changed once the program is running. Go uses the const keyword to declare them.

Unlike variables (declared with var or :=), constants are not stored in memory at runtime. The compiler evaluates the value at build time and substitutes it directly into the generated machine code — a process called constant folding. This makes constants both faster and safer than variables for values that should never change.

Constants can only be boolean, numeric (integer, float, complex), string, or rune types. You cannot make a constant from a slice, map, or struct — those are reference types that require runtime allocation.

Real-World Analogyclick to expand

Think of constants like the speed of light or pi — values that are universal and never change. In a real codebase, constants represent things like HTTP status codes, configuration limits, mathematical values, and enumerated states.

If you're building an API server, const MaxRequestBodySize = 10 * 1024 * 1024 (10 MB) is a constant because it's decided at build time, not while the server is running. A variable would be used for the current request body size, which changes with every request.

# Declaring Constants

Go gives you three ways to declare constants — a single value, a typed value, or a grouped block:

package main

import "fmt"

// Single untyped constant — adapts to context
const Pi = 3.14159

// Typed constant — restricted to int only
const MaxRetries int = 3

// Const block — group related values together
const (
    AppName    = "MyService"
    AppVersion = "1.0.0"
    MaxConns   = 100
)

func main() {
    fmt.Println(AppName, AppVersion)
    fmt.Println("Pi =", Pi)
    fmt.Println("Max retries:", MaxRetries)
}

# Breaking It Down

const Pi = 3.14159

This creates an untyped constant. The value 3.14159 has no explicit type — the compiler gives it the kind "untyped float". It can be used anywhere a float32, float64, or even a complex128 is expected, without casting.

const MaxRetries int = 3

This is a typed constant. It can only be used where an int is expected. You cannot assign it to a float64 variable without an explicit conversion. Typed constants are stricter but make intent clearer.

const ( ... )

A const block groups related constants together. This is idiomatic Go — it keeps your namespace clean and signals that these values are logically related. It also enables iota, which only works inside a const block.

# Typed vs Untyped Constants

This distinction is one of the most important things to understand about Go constants. Untyped constants are flexible — typed constants are strict.

// Untyped — adapts to the surrounding type
const x = 10
var i int     = x   // works
var f float64 = x   // works — no cast needed
var b byte    = x   // works — 10 fits in a byte

// Typed — locked to one type
const y int = 10
var j int     = y   // works
// var g float64 = y   // compile error: cannot use y (int) as float64
var g float64 = float64(y) // explicit conversion required

Rule of thumb: use untyped constants (no explicit type) by default. Use typed constants only when you want to prevent the value from being used in the wrong type context.

# iota — Auto-incrementing Constants

iota is Go's way of creating enumerations. It starts at 0 in each const block and increments by 1 for each successive constant. If you omit the expression on a line, the previous expression (with new iota value) is repeated.

type Weekday int

const (
    Sunday    Weekday = iota // 0
    Monday                   // 1
    Tuesday                  // 2
    Wednesday                // 3
    Thursday                 // 4
    Friday                   // 5
    Saturday                 // 6
)

fmt.Println(Monday)   // 1
fmt.Println(Saturday) // 6
Why create a Weekday type?click to expand

Declaring type Weekday int creates a named type distinct from plain int. This gives you compile-time safety: a function that accepts Weekday will reject a random int. It also lets you attach a String() method so fmt.Println(Monday) can print "Monday" instead of "1".

# Common iota Patterns

Power-of-2 Bit Flags

Combine iota with left-shift to create bitmask permission flags:

const (
    ReadPerm  = 1 << iota // 1  (binary: 001)
    WritePerm             // 2  (binary: 010)
    ExecPerm              // 4  (binary: 100)
)

perms := ReadPerm | WritePerm // 3 (binary: 011)
if perms&ReadPerm != 0 {
    fmt.Println("can read") // printed
}
if perms&ExecPerm != 0 {
    fmt.Println("can execute") // NOT printed
}

Byte Size Units

Skip the zero value with a blank identifier and use iota for data sizes:

const (
    _  = iota             // skip 0
    KB = 1 << (10 * iota) // 1 << 10 = 1024
    MB                    // 1 << 20 = 1,048,576
    GB                    // 1 << 30 = 1,073,741,824
    TB                    // 1 << 40 = 1,099,511,627,776
)

fmt.Println("1 KB =", KB, "bytes")
fmt.Println("1 GB =", GB, "bytes")

Skipping and Resetting

Use the blank identifier _ to skip values, and remember that iota resets in each const block:

const (
    _       = iota // 0 — discarded
    Bronze         // 1
    Silver         // 2
    Gold           // 3
)

// New const block — iota resets to 0
const (
    Red   = iota // 0
    Green        // 1
    Blue         // 2
)

# Constants in Production Code

Here's how constants are used in real Go projects:

// HTTP server configuration
const (
    DefaultPort         = 8080
    DefaultReadTimeout  = 30 // seconds
    DefaultWriteTimeout = 30
    MaxHeaderBytes      = 1 << 20 // 1 MB
)

// Application-level limits
const (
    MaxUploadSize   = 50 * MB   // uses our MB constant
    MaxPageSize     = 100
    DefaultPageSize = 20
)

// Feature flags (compile-time)
const DebugMode = false

Notice how constants make your code self-documenting. Reading MaxHeaderBytes = 1 << 20 is much clearer than seeing a magic number 1048576 scattered through your code. If you need to change a limit, you update one constant instead of hunting through every file.

# Try It Yourself

Define a LogLevel type with constants Debug, Info, Warning, Error, Fatal using iota. Then write a function LevelName(l LogLevel) string that returns the string name of each level.

⚡ Key Takeaways

  • const declares compile-time values — they're substituted directly into machine code
  • Constants can only be bool, numeric, string, or rune types — no slices, maps, or structs
  • Untyped constants adapt to their surrounding type; typed constants are strict
  • iota starts at 0 in each const block and auto-increments — perfect for enums
  • Use 1 << iota for bit flags, _ to skip values
  • Named types like type Weekday int give type safety to iota-based constants
practice & review