Constants and Literals

Updated

September 13, 2026

Constants and Literals

A constant is a value the compiler knows. The boring default is const for names that must not change (prices, bit sizes, shift labels), untyped until a type is useful, and iota only for a dense list of related integers.

Mental model

const n = 4 is untyped. It can become int or int64 at the use site. const n int = 4 is typed and stays int.

iota is a counter that starts at 0 in each const block and goes up by one per line. A line with no expression repeats the previous expression, which is how iota “fills down.”

Numeric literals may use _ as a digit separator. Rune literals are code points in single quotes: 'A', '€', '\n'.

Worked examples

Case 1: Named prices that cannot drift

Save as menu_const.go. Cents are integers. The names are the documentation.

// menu_const.go
package main

import "fmt"

const (
    toastCents = 350
    teaCents   = 250
    soupCents  = 600
)

func main() {
    total := toastCents + teaCents
    fmt.Println(total)
    fmt.Printf("%T\n", toastCents)
}

Run:

go run menu_const.go

Output:

600
int

toastCents is untyped until fmt.Printf("%T") gives it the default type int. You still cannot assign to it: there is no toastCents = 1.

Case 2: Untyped vs typed constants

Save as typed_const.go. The untyped seat count fits both widths. The typed one needs a conversion.

// typed_const.go
package main

import "fmt"

func main() {
    const seats = 4
    var i int = seats
    var w int64 = seats

    const typedSeats int = 4
    var also int64 = int64(typedSeats)

    fmt.Println(i, w, also)
}

Run:

go run typed_const.go

Output:

4 4 4

var also int64 = typedSeats does not compile. Typed constants follow the same conversion rules as typed variables.

Case 3: iota for a small set of states

Save as ticket_state.go. Consecutive integers with names beat magic 0 and 1 in a switch.

// ticket_state.go
package main

import "fmt"

const (
    StateOpen = iota
    StateSeated
    StateOrdered
    StatePaid
)

func label(s int) string {
    switch s {
    case StateOpen:
        return "open"
    case StateSeated:
        return "seated"
    case StateOrdered:
        return "ordered"
    case StatePaid:
        return "paid"
    default:
        return "unknown"
    }
}

func main() {
    fmt.Println(StateOpen, label(StateOpen))
    fmt.Println(StatePaid, label(StatePaid))
}

Run:

go run ticket_state.go

Output:

0 open
3 paid

Skip a value with _: const ( _ = iota; Morning; Evening ) gives Morning = 1, Evening = 2.

Case 4: Numeric literals and rune literals

Save as literals.go. Underscores are for readers. Bases are 0b, 0o, 0x. A rune is an integer.

// literals.go
package main

import "fmt"

func main() {
    cents := 1_250
    flags := 0b_0000_0101
    table := 0x0C
    euro := '€'
    fmt.Println(cents)
    fmt.Println(flags)
    fmt.Println(table)
    fmt.Printf("%q %d %U\n", euro, euro, euro)
}

Run:

go run literals.go

Output:

1250
5
12
'€' 8364 U+20AC

1_250 is one thousand two hundred fifty, not “a tuple.” The underscores are not digits.

Case 5: Bit flags with 1 << iota

Save as bit_flags.go. When modeling permissions, roles, or operational modes, shifting 1 by iota creates distinct power-of-two bit flags that can be combined with bitwise OR (|) and tested with bitwise AND (&).

// bit_flags.go
package main

import "fmt"

const (
    PermRead  = 1 << iota // 1 << 0 == 1
    PermWrite             // 1 << 1 == 2
    PermAudit             // 1 << 2 == 4
)

func main() {
    role := PermRead | PermWrite
    hasRead := role&PermRead != 0
    hasAudit := role&PermAudit != 0

    fmt.Println("role flags:", role)
    fmt.Println("can read:", hasRead)
    fmt.Println("can audit:", hasAudit)
}

Run:

go run bit_flags.go

Output:

role flags: 3
can read: true
can audit: false

Bitmasks let you represent multi-attribute status in a single integer without slices or maps.

Case 6: Arbitrary precision in constant math

Save as big_const.go. Untyped constants in Go are evaluated at compile time with arbitrary precision (at least 256 bits of precision). Intermediate expressions can exceed the limits of int64 without overflowing, as long as the final assigned value fits the target type.

// big_const.go
package main

import "fmt"

const (
    _   = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
)

func main() {
    fmt.Println("KiB:", KiB)
    fmt.Println("MiB:", MiB)
    fmt.Println("GiB:", GiB)

    // Intermediate calculation exceeds int64, but result fits in int
    const huge = (1 << 100) / (1 << 90)
    fmt.Println("huge ratio:", huge)
}

Run:

go run big_const.go

Output:

KiB: 1024
MiB: 1048576
GiB: 1073741824
huge ratio: 1024

1 << 100 would immediately overflow a 64-bit variable, but in constant arithmetic it evaluates cleanly.

The trap

iota keeps counting even when you write a different expression. The next line repeats that expression, not “the next iota.” Save as iota_repeat.go:

// iota_repeat.go
package main

import "fmt"

func main() {
    const (
        open    = iota // 0
        seated         // 1
        hold    = 10
        ordered        // 10 again — repeats 10, iota is unused
        paid    = iota // 4 — iota still counted the lines
    )
    fmt.Println(open, seated, hold, ordered, paid)
}

Run:

go run iota_repeat.go

Output:

0 1 10 10 4

ordered is not 3. It is 10. If you need a hole, use _ = iota or write every value. If the numbers must be stable on the wire, write the numbers (StatePaid = 3) and stop using iota for that block.

The boring rule

  • const for values that are part of the program, not the request.
  • Leave constants untyped unless a typed const documents a width.
  • iota for dense, internal enumerations and bit flags (1 << iota).
  • Do not use iota for wire protocol or database codes that must remain immutable across versions.
  • Rely on compile-time arbitrary precision for byte size calculations (1 << 20 for MiB).
  • Use _ in long numbers: 1_000_000, not 1000000.
  • Rune literals for code points. String literals for text.
  • One const block per family of names.

Try this

  1. In menu_const.go, try toastCents = 1 inside main. Read the compile error.
  2. In bit_flags.go, add PermAdmin = 1 << iota and check if a user with PermAdmin | PermRead has admin privileges.
  3. In big_const.go, calculate TiB as the next line in the iota block.
  4. Add a typed const soupCents int64 = 600 and add it to an int total. Convert one side.
  5. In ticket_state.go, insert _ = iota as the first line so StateOpen becomes 1.