Constants & iota

Reviewed & published by Brayan K

A constant is a value fixed at compile time that can never change, and iota is Go's built-in counter for generating sequences of related constants like enums and bit flags.

Part of the free Go course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Declaring Constants

You declare a constant with the const keyword. Because the value is fixed at compile time, the compiler can compute expressions like 60 * 60 * 24 up front — but it will reject anything it can't evaluate, like a function-call result. You can declare constants one at a time or group related ones in a const ( ... ) block. Reassigning a constant is a compile error, which makes constants a great way to lock down values that must never drift.

package main

import "fmt"

func main() {
    // A constant is a value fixed at compile time — it can never change.
    const Pi = 3.14159
    const Greeting = "hello"

    // Constants can be declared in a block, like a list.
    const (
        MaxUsers = 100
        AppName  = "LearnGo"
    )

    fmt.Println(Pi, Greeting)
    fmt.Println(MaxUsers, AppName)

    // Trying to change a constant is a COMPILE error, so we can't even show it.
    // Pi = 3.2   // ❌ cannot assign to Pi

    // Constants are evaluated at compile time, so they can build on each other.
    const SecondsPerDay = 60 * 60 * 24
    fmt.Println("seconds in a day:", SecondsPerDay)
}

2️⃣ Typed vs Untyped Constants

An untyped constant (like const big = 1 << 40) has no fixed type until you actually use it — it adapts to whatever context it lands in, so the same constant can become a float64 in one place and an int64 in another. A typed constant (const maxRetries int = 5) is locked to one type, so mixing it with a different type needs an explicit conversion. Leaving constants untyped is usually the more flexible choice.

package main

import "fmt"

func main() {
    // An UNTYPED constant has no fixed type until it's used. It adapts.
    const big = 1 << 40 // way bigger than an int32, but fine as untyped

    var f float64 = big // becomes a float64 here
    var i int64 = big   // and an int64 here — same constant, two types
    fmt.Println(f, i)

    // Untyped constants keep high precision and fit wherever they're valid.
    const third = 1.0 / 3.0
    fmt.Printf("%.10f\n", third)

    // A TYPED constant is locked to one type.
    const maxRetries int = 5
    var attempts int = maxRetries // ok: both int
    fmt.Println(attempts)

    // Mixing a typed constant with another type needs a conversion:
    var ratio float64 = float64(maxRetries) / 2
    fmt.Println(ratio)
}

3️⃣ iota: The Enumerator

iota is a built-in counter that resets to 0 at the start of every const block and increases by 1 for each line — whether or not iota appears on that line. That makes it perfect for enum-style constants like the days of the week. You can skip a value by assigning a line to the blank identifier _, and you can shift the whole sequence with arithmetic such as 1 << (10 * iota) to build a kilobyte / megabyte ladder.

package main

import "fmt"

// iota is a counter that resets to 0 in each const block and
// increments by 1 for every line. It's Go's enum builder.
type Weekday int

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

func main() {
    fmt.Println(Sunday, Wednesday, Saturday) // 0 3 6

    // You can start from a different value and skip with the blank _.
    const (
        _  = iota             // skip 0
        KB = 1 << (10 * iota) // 1 << 10
        MB                    // 1 << 20
        GB                    // 1 << 30
    )
    fmt.Println(KB, MB, GB)
}

4️⃣ Bit Flags with iota

Shifting 1 << iota gives you 1, 2, 4, 8, ... — each a single distinct bit. Because no two flags share a bit, you can pack several on/off options into one integer: combine them with | (OR), test them with & (AND), and add one with |=. This is the classic permission-bits pattern (Read, Write, Execute).

package main

import "fmt"

// Bit flags: each constant is a single bit, so they combine with | (OR).
type Permission uint8

const (
    Read    Permission = 1 << iota // 1  (binary 001)
    Write                          // 2  (binary 010)
    Execute                        // 4  (binary 100)
)

func main() {
    // Combine flags with OR.
    p := Read | Write
    fmt.Printf("combined = %03b (%d)\n", p, p)

    // Test a flag with AND.
    fmt.Println("can read?   ", p&Read != 0)
    fmt.Println("can execute?", p&Execute != 0)

    // Add a flag.
    p |= Execute
    fmt.Printf("now = %03b (%d)\n", p, p)
}

🎯 Your Turn

Fill in the blank so Small, Medium, and Large become 0, 1, and 2. Remember that only the first line needs iota — the rest inherit it.

package main

import "fmt"

type Size int

const (
    Small Size = ___   // 👉 iota   -> 0
    Medium             // 👉 (blank) -> 1
    Large              // 👉 (blank) -> 2
)

func main() {
    fmt.Println(Small, Medium, Large)
    // ✅ Expected output:  0 1 2
}

Common Errors (and the fix)

🎯 Mini-Challenge: Reach a Terabyte

Extend the byte-size ladder. In one const block, skip 0, then use 1 << (10 * iota) for KB, MB, GB, and add a TB line. Print TB and match the expected output.

package main

import "fmt"

// 🎯 MINI-CHALLENGE: extend the byte-size ladder to TB.
// Using one const block with iota and 1 << (10 * iota),
// add a TB line after GB and print TB.
//
// ✅ Expected output:  1099511627776

func main() {
    // your code here
}

🎉 Lesson Complete!

Practice quiz

When is a const value fixed?

  • At run time
  • On first use
  • At compile time
  • When garbage collected

Answer: At compile time. A constant is fixed at compile time, so the compiler can bake its value directly into the program.

Which of these is a valid constant declaration?

  • const Pi = 3.14159
  • const start = time.Now()
  • const r = rand.Int()
  • const now = os.Getenv("X")

Answer: const Pi = 3.14159. Constants must be compile-time values. A function call result like time.Now() is not allowed.

What value does iota have on the first line of a const block?

  • 1
  • -1
  • Whatever you assign
  • 0

Answer: 0. iota starts at 0 on the first line of a const block and increments by 1 for each subsequent line.

Given const ( Sunday = iota; Monday; Tuesday; Wednesday ), what is Wednesday?

  • 0
  • 3
  • 1
  • 4

Answer: 3. iota is 0 for Sunday, then 1, 2, 3 — so Wednesday is 3.

What does an untyped constant like const big = 1 << 40 give you?

  • No fixed type until used; it adapts to context
  • A fixed int type
  • A float64 always
  • A compile error

Answer: No fixed type until used; it adapts to context. An untyped constant has no fixed type until used, so the same constant can become a float64 or an int64.

With 1 << iota for Read, Write, Execute, what are their values?

  • 0, 1, 2
  • 1, 2, 3
  • 1, 2, 4
  • 2, 4, 8

Answer: 1, 2, 4. 1 << iota gives 1, 2, 4 — each a distinct single bit, ideal for combinable flags.

Given Read=1, Write=2, what does Read | Write equal?

  • 1
  • 3
  • 2
  • 0

Answer: 3. Bitwise OR of 001 and 010 is 011, which is 3.

What does an iota line that omits its expression do?

  • Sets the value to 0
  • Causes a compile error
  • Copies the previous value exactly
  • Repeats the previous line's expression with the new iota

Answer: Repeats the previous line's expression with the new iota. A line without an expression repeats the previous line's expression, evaluated with the new iota value.

How do you skip the value 0 at the start of an iota block?

  • start = 1
  • Assign the first line to _ (e.g. _ = iota)
  • Use iota--
  • You cannot skip it

Answer: Assign the first line to _ (e.g. _ = iota). Assigning the first line to the blank identifier _ discards 0 so the real values start at the next iota.

Using 1 << (10 * iota) with _ skipping 0, what is the value after KB, MB, GB (the TB line)?

  • 1073741824
  • 1048576
  • 1099511627776
  • 549755813888

Answer: 1099511627776. iota reaches 4 on the TB line, so 1 << 40 = 1099511627776.

Continue this course

Frequently asked questions

What is the difference between const and a regular variable?

A const is fixed at compile time and can never be reassigned — the compiler bakes its value into the program. A var holds a value at run time that you can change. Constants must be values the compiler can compute up front (numbers, strings, booleans, and expressions over them); you cannot make a constant from a function call result or a slice.

What does 'typed vs untyped constant' actually mean?

An untyped constant like const x = 5 has no fixed type until you use it, so it can flow into an int, a float64, or any compatible type without a conversion. A typed constant like const x int = 5 is locked to that one type, so combining it with a different type requires an explicit conversion. Leaving constants untyped is usually the more flexible choice.

What is iota and when does it reset?

iota is a built-in counter used inside const blocks. It starts at 0 on the first line of a const block and increases by 1 for each subsequent line (whether or not iota appears on that line). It resets to 0 at the start of every new const block, which makes it perfect for numbering related constants like enums.

Why is 1 << iota used for bit flags?

Shifting 1 left by iota gives you 1, 2, 4, 8, ... — each a single distinct bit. Because no two flags share a bit, you can combine them with | (OR) and test them with & (AND). This lets one integer carry several on/off options at once, like Read|Write.

How do I skip a value with iota?

Assign the line to the blank identifier _, for example '_ = iota' as the first line to throw away 0. You can also skip several values or shift the sequence with arithmetic such as iota + 1. Any line that omits an expression repeats the previous line's expression with the new iota value.

Related lessons