The iota Identifier

How iota generates incrementing constants in Go with patterns for enumerations bit-shift scales and bitmask flags

How iota Eliminates Manual Value Assignment

When you need a set of related integer constants — days of the week, error codes, configuration states — you could assign each value by hand. That works for three or four entries, but it becomes tedious and error-prone as the list grows. Every insertion or removal forces you to renumber. Go's iota identifier removes that labor entirely.

iota is a counter that the compiler maintains inside each const block. It starts at 0 for every new const keyword, increments by 1 after each constant specification (each line where a constant is declared), and resets completely when the block ends. This gives you auto-numbering without any runtime cost — all values are resolved at compile time.

A compile-time counter only:

iota is not a variable, not a runtime value, and not accessible outside a const declaration. You cannot read it with fmt.Println(iota) or use it in an if statement. It exists purely for the compiler to fill in increments.

The Mechanics of iota in const Blocks

Consider a list of weekdays. Manually, it would look like this:

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

With iota, you write it once and let the compiler fill in the rest:

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

Two rules make this work. First, iota itself is an integer that advances: it is 0 on the first line, 1 on the second, and so on. Second, within a const block, if a line omits its expression, it reuses the previous line's expression — including the iota part — but with iota already incremented for the new position.

So Monday on its own is equivalent to Monday = iota with the current iota value of 1. The pattern repeats without you typing iota on every line.

Check your output:

If you print these constants you will see they hold the values 0 through 6 exactly as if you had typed them manually. The compiler does the work, and the output is identical to the hand-numbered version.

Enumeration Constants with iota and Custom Types

By itself, iota produces plain int values. They are compatible with any integer operation, which can be convenient but also unsafe — you might accidentally pass a weekday constant where an unrelated integer is expected, or vice versa. Go does not have built-in enums, but combining iota with a custom type gives you compile-time type safety that behaves like one.

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

Now Sunday is not an int; it is a Weekday. A function that accepts Weekday will reject a bare integer literal like 3 — unless that literal is an untyped constant, in which case Go’s constant flexibility allows it. But a variable of type int will cause a compile error if passed where Weekday is expected.

This is how you create domain-specific sets in Go: a Status type for states, a LogLevel for severity, a Priority for task ordering. The custom type makes the intent visible and prevents accidental mixing of unrelated integer categories.

type Status int
const (
    StatusPending   Status = iota
    StatusActive
    StatusCompleted
    StatusFailed
)

To make these values human-readable, implement the String() method:

func (s Status) String() string {
    switch s {
    case StatusPending:
        return "Pending"
    case StatusActive:
        return "Active"
    case StatusCompleted:
        return "Completed"
    case StatusFailed:
        return "Failed"
    default:
        return "Unknown"
    }
}

fmt.Println(StatusActive) will now print Active instead of 1.

Untyped integer constants still pass through:

A function that takes a Weekday will accept 0 directly because 0 is an untyped constant that can represent any integer type. This is a deliberate language feature, not a bug, but it can surprise you. If you want strict enforcement, do not rely solely on the custom type; validate ranges with a helper function.

Skipping Values

Not every sequence maps to consecutive integers. Some systems reserve values or define gaps — for example, audio outputs where mono is 1, stereo is 2, and surround starts at 5. You need 3 and 4 to be unavailable.

The blank identifier _ absorbs iota increments without creating a named constant. iota advances as usual; the line simply produces no accessible result.

type AudioOutput int
const (
    OutMute  AudioOutput = iota // 0
    OutMono                     // 1
    OutStereo                   // 2
    _                           // 3 (skipped)
    _                           // 4 (skipped)
    OutSurround                 // 5
)

The value 3 and 4 are never usable under any name. If you print OutSurround, you get 5. This is far cleaner than leaving explicit numbers that someone might later misinterpret as usable.

Bit-Shift Patterns for Orders of Magnitude

The real power of iota emerges when you combine it with expressions that depend on its value. The classic example is defining byte-size constants like kilobyte, megabyte, and gigabyte using powers of two.

type ByteSize float64
const (
    _  = iota             // ignore first value 0
    KB ByteSize = 1 << (10 * iota) // 1 << (10*1) = 1024
    MB                           // 1 << (10*2) = 1048576
    GB                           // 1 << (10*3) = 1073741824
    TB                           // 1 << (10*4)
    PB                           // 1 << (10*5)
)

Here is what happens line by line. On the first line, iota is 0, and the blank identifier discards it. On the second line, iota is 1, so the expression 1 << (10 * iota) becomes 1 << 10, which equals 1024. On the third line, no expression is written, so the previous expression is reused — but with iota now incremented to 2. That yields 1 << 20, which is 1048576. The pattern repeats through terabytes, petabytes, and beyond.

This is far more maintainable than manually typing 1 << 10, 1 << 20, 1 << 30. If you ever need to add a new unit, you just append one line. If you switch from base-2 to base-10 definitions, you change a single expression.

Check your shift amounts:

Large shift amounts like 1 << 60 and beyond can overflow default integer types on 32-bit platforms. The ByteSize type in the example is float64, which avoids integer overflow but still requires caution. For integer-based constants, use uint64 or test on your target architecture.

Bitmask Flags with iota

When each value in a set is a distinct power of two, you can combine them with bitwise OR to represent multiple states simultaneously. iota makes this compact.

type Permission uint8
const (
    PermRead    Permission = 1 << iota // 1 << 0 = 0b00000001
    PermWrite                          // 1 << 1 = 0b00000010
    PermExecute                        // 1 << 2 = 0b00000100
    PermDelete                         // 1 << 3 = 0b00001000
)

The expression 1 << iota shifts a single bit into a unique position. With this, you can create combined permissions:

userPerm := PermRead | PermWrite | PermExecute

Testing a specific flag uses bitwise AND:

func (p Permission) Has(flag Permission) bool {
    return p&flag != 0
}

This gives you compact, compile-time-checked flags. Unlike a slice of strings, a bitmask fits in a single byte or integer and can be compared with a single operation. The naming makes it clear which bits belong to which permission.

Multiple Constants on One Line

A single line in a const block can declare multiple constants, separated by commas. iota increments once per line, not once per constant. This leads to a surprising outcome if you expect each constant to receive a distinct iota.

const (
    A, B = iota + 1, iota + 2  // iota = 0 → A=1, B=2
    C, D                        // iota = 1 → C=2, D=3
    E, F                        // iota = 2 → E=3, F=4
)

The first line sets A = 0 + 1 and B = 0 + 2. iota is still 0 for both assignments on that line; it does not increment partway through. On the next line, iota becomes 1. The expression list iota + 1, iota + 2 is reused, giving C = 1 + 1 = 2 and D = 1 + 2 = 3. Notice that B and C both end up as 2. The values overlap.

Duplicate values from same-line iota:

If you need distinct values for each constant on a single line, you must write expressions that use the same iota value to produce different results — for example, iota * 2 and iota * 2 + 1. Otherwise, expect duplicate constants.

For parallel sequences that do not overlap, you can use iota on each line with separate counters. However, a single iota cannot produce two independent progressions. If that is what you need, use two separate const blocks.

Iota Resets Per const Block

Every occurrence of the const keyword starts a fresh iota sequence. You can have multiple const blocks in the same file, or even in the same function, and each block will begin counting from 0 again.

const (
    A = iota // 0
    B        // 1
)
const (
    C = iota // 0 (reset)
    D        // 1
)

This makes iota self-contained. You do not need to worry about interfering with values from a different declaration group. The reset is automatic and complete.

No limit to number of blocks:

You can have as many const blocks as you need. Each one independently manages its own iota progression, which is especially useful for grouping unrelated constant sets in the same package.

Common Mistakes

Several misunderstandings about iota appear repeatedly. Knowing them in advance saves debugging time.

Using iota outside a const block. iota is a reserved identifier that only has meaning inside const declarations. Writing var x = iota or fmt.Println(iota) produces an "undefined: iota" compile error. It is not a variable or a function.

Expecting iota to persist across blocks. Each const block resets iota to 0. If you want a continuing sequence across multiple blocks, you must manage it manually — there is no persistent counter.

Confusion with multiple constants on the same line. As shown earlier, iota increments per line, not per constant. A single line with three constants still only advances iota by one for the next line.

Forgetting that blank lines do not skip values. Empty lines in the source (just whitespace) are not constant specifications; they do not affect iota. Only a line that actually declares a constant — including one with a blank identifier — advances iota. If you want to skip a numeric value, use _ = iota or simply _.

Assuming iota is always int. iota itself is an untyped integer constant. When used in an expression, its exact type is determined by the context. It can appear in shifts that produce uint, int64, or even float64 results.

Attempting to redefine iota. You cannot declare your own variable named iota. It is a predeclared identifier that is always reserved for constant declarations.

Real-World Usage

In practice, iota is the standard Go idiom whenever a set of related integer constants appears. Here are a few concrete patterns.

  • State machines. A State type with values like StateIdle, StateRunning, StateStopped defined via iota. Adding a new state is a single line insertion.
  • Error codes in a library. A custom ErrCode type using iota to assign sequential codes, paired with a String() method for readable logs.
  • Log levels. DEBUG, INFO, WARN, ERROR as increasing integers, allowing comparisons like if level >= WARN.
  • Configuration constants for bitmask options. As shown with Permission, allowing a single integer to encode multiple choices.

Note that iota is not suitable when constants must have specific, non-sequential values — such as HTTP status codes (200, 404, 500) or POSIX signal numbers. In those cases, explicit assignment is clearer and avoids confusion.