const and iota — Junior Level¶
Table of Contents¶
- Introduction
- Prerequisites
- Glossary
- Core Concepts
- Real-World Analogies
- Mental Models
- Pros & Cons
- Use Cases
- Code Examples
- Coding Patterns
- Clean Code
- Product Use / Feature
- Error Handling
- Security Considerations
- Performance Tips
- Metrics & Analytics
- Best Practices
- Edge Cases & Pitfalls
- Common Mistakes
- Common Misconceptions
- Tricky Points
- Test
- Tricky Questions
- Cheat Sheet
- Self-Assessment Checklist
- Summary
- What You Can Build
- Further Reading
- Related Topics
- Diagrams & Visual Aids
Introduction¶
In Go, const lets you define values that never change after they are declared. Think of them as labels glued onto fixed values — once the label is printed, it cannot be peeled off and replaced. The iota identifier is a special helper that lives inside const blocks and automatically generates sequential integer values, making it very easy to create enumerated types (enums).
These two features work hand-in-hand to let you write code that is safer, more readable, and less error-prone than code full of raw numbers scattered everywhere.
Prerequisites¶
Before learning const and iota you should be comfortable with:
- Declaring variables with
varand:= - Basic Go types:
int,float64,string,bool - Writing and running simple Go programs
- Understanding what a package is
Glossary¶
| Term | Meaning |
|---|---|
| constant | A value fixed at compile time that cannot be changed at runtime |
| iota | A predeclared identifier in Go that generates incrementing integers inside a const block |
| untyped constant | A constant with no explicit type; Go gives it a "default type" when needed |
| typed constant | A constant declared with an explicit type like const X int = 5 |
| enum | A named set of related constants (Go does not have a built-in enum keyword) |
| compile time | The phase when the Go compiler translates your source code into an executable |
| magic number | A literal number in code whose meaning is not obvious without context |
| const block | A group of constants declared together with const ( ... ) |
Core Concepts¶
1. Declaring a Constant¶
These are untyped constants. Go figures out their type when you use them.
2. Typed Constants¶
Here you explicitly say what type the constant is.
3. Group Declaration¶
You can group multiple constants together:
4. const vs var¶
var score = 10 // can change: score = 20 is fine
const limit = 10 // cannot change: limit = 20 will NOT compile
The key rule: const values must be known at compile time. You cannot do:
5. What iota Does¶
iota is a counter that starts at 0 inside every new const block and increases by 1 for each line:
Because the pattern is repeated, Go lets you write it more concisely — once you write an expression with iota, every following line in the same block repeats that expression with the new iota value:
Both snippets produce the same result.
6. iota Resets in Each Block¶
Real-World Analogies¶
Analogy 1 — Constants as Printed Signs¶
Imagine a sign on a motorway that says "Speed Limit: 100 km/h". That sign was printed once and cannot be changed by passers-by. A const is exactly like that sign — printed at compile time, readable by everyone, but never writable.
Analogy 2 — iota as a Ticket Machine¶
Picture a ticket dispenser at a bakery. Every time someone pulls a ticket, the number increases by 1: 0, 1, 2, 3 ... When the bakery opens the next day (new const block), the machine resets to 0.
Analogy 3 — Group const as a Menu¶
A restaurant menu lists items in order: starter, main, dessert. Each item has a fixed price. A const block is like a laminated menu — all values are fixed, printed in order, and cannot be changed by the waiter.
Mental Models¶
Model 1 — The Whiteboard That Cannot Be Erased A constant is written on a whiteboard in permanent marker. You can look at it, copy it, use it in calculations, but you can never erase or overwrite it.
Model 2 — iota as Row Numbers in a Spreadsheet When you list items in a const block, think of each row getting an automatic row number starting from 0. That row number is iota.
Pros & Cons¶
Pros¶
| Benefit | Description |
|---|---|
| Safety | The compiler prevents accidental modification |
| Readability | Named constants make code self-documenting |
| Performance | Constants are resolved at compile time — no runtime overhead |
| Maintenance | Change a constant in one place; all uses update automatically |
| Type safety (typed consts) | Typed constants prevent mixing unrelated values |
Cons¶
| Limitation | Description |
|---|---|
| Not dynamic | Cannot hold a value computed at runtime |
| Limited types | Cannot be slices, maps, arrays, or most structs |
| iota can be fragile | Inserting a new constant in the middle shifts all following values |
Use Cases¶
- HTTP status codes —
StatusOK = 200,StatusNotFound = 404 - Days of the week —
Monday,Tuesday, ... usingiota - Directions —
North,East,South,West - File permissions — Read, Write, Execute as bit flags
- Mathematical constants —
Pi,E,GoldenRatio - Configuration limits —
MaxConnections = 100,TimeoutSeconds = 30 - Byte sizes —
KB,MB,GBusingiota
Code Examples¶
Example 1 — Simple Constants¶
package main
import "fmt"
const (
AppName = "MyApp"
AppVersion = "1.0.0"
MaxUsers = 1000
)
func main() {
fmt.Println("Application:", AppName)
fmt.Println("Version:", AppVersion)
fmt.Printf("Supports up to %d users\n", MaxUsers)
}
Output:
Example 2 — Days of the Week with iota¶
package main
import "fmt"
type Day int
const (
Sunday Day = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
func main() {
today := Wednesday
fmt.Println("Today is day number:", today) // 3
}
Example 3 — Skipping iota Value 0¶
A common pattern: skip 0 so that the zero value of the type means "unknown" or "unset":
package main
import "fmt"
type Color int
const (
_ Color = iota // 0 — skip, means "unknown"
Red // 1
Green // 2
Blue // 3
)
func main() {
var c Color // zero value = 0 = unknown
fmt.Println("Default color:", c) // 0
fmt.Println("Red:", Red) // 1
}
Example 4 — File Size Constants¶
package main
import "fmt"
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)
func main() {
fileSize := 5 * MB
fmt.Printf("File size: %d bytes\n", fileSize)
fmt.Printf("That is %d MB\n", fileSize/MB)
}
Example 5 — HTTP Status Codes¶
package main
import "fmt"
const (
StatusOK = 200
StatusCreated = 201
StatusBadReq = 400
StatusNotFound = 404
StatusInternal = 500
)
func handleStatus(code int) {
switch code {
case StatusOK:
fmt.Println("Success!")
case StatusNotFound:
fmt.Println("Resource not found.")
case StatusInternal:
fmt.Println("Server error.")
default:
fmt.Println("Unknown status:", code)
}
}
func main() {
handleStatus(StatusOK)
handleStatus(StatusNotFound)
handleStatus(503)
}
Coding Patterns¶
Pattern 1 — Enum with Named Type¶
Always give your iota constants a named type. This prevents mixing unrelated constants:
Pattern 2 — Skip Zero Value¶
type Priority int
const (
_ Priority = iota // unused zero value
PriorityLow // 1
PriorityMedium // 2
PriorityHigh // 3
PriorityCritical // 4
)
Pattern 3 — Simple Bit Flags¶
type Permission int
const (
Read Permission = 1 << iota // 1
Write // 2
Execute // 4
)
func main() {
userPerm := Read | Write // 3
if userPerm&Read != 0 {
fmt.Println("Can read")
}
if userPerm&Execute != 0 {
fmt.Println("Can execute")
} else {
fmt.Println("Cannot execute")
}
}
Clean Code¶
Do: Use Named Constants Instead of Magic Numbers¶
// Bad
if retries > 3 {
return error
}
// Good
const MaxRetries = 3
if retries > MaxRetries {
return error
}
Do: Group Related Constants Together¶
Do: Use Descriptive Names¶
// Bad
const (
A = iota
B
C
)
// Good
const (
DirectionNorth Direction = iota
DirectionEast
DirectionSouth
DirectionWest
)
Product Use / Feature¶
In a real application, constants are used for:
- Feature flags (as typed boolean constants if the value is known at compile time)
- Configuration defaults like timeout values, retry limits, max page sizes
- API response codes so every part of the code uses the same named value
- Roles and permissions using bit flags for compact storage
Example — a user role system:
type Role int
const (
RoleGuest Role = iota
RoleUser
RoleEditor
RoleAdmin
)
func canEdit(r Role) bool {
return r >= RoleEditor
}
Error Handling¶
Constants themselves do not cause runtime errors. However, common pitfalls include:
- Using the zero value of an iota enum — if you forget to skip 0, the zero value of any variable of that type will silently equal the first constant.
type Direction int
const (
North Direction = iota // North is 0
East
South
West
)
var d Direction // d == 0 == North — is that what you wanted?
- Typed constant assignment mismatch
Fix by using an untyped constant or explicit conversion:
Security Considerations¶
- Sensitive values should not be constants — API keys, passwords, and tokens must come from environment variables or secret managers, never from source-code constants.
- iota-based permission flags must be designed carefully; accidentally leaving a permission at 0 may grant or deny access unexpectedly.
Performance Tips¶
- Constants are resolved entirely at compile time. There is zero runtime cost to using them.
- Replacing repeated literal values with constants does not slow the program — the compiler substitutes the literal value at each use site.
- Using constants in expressions (like
5 * MB) allows the compiler to fold the expression into a single literal — this is called constant folding.
Metrics & Analytics¶
When building analytics or logging systems, named constants improve log readability:
type EventType int
const (
EventLogin EventType = iota
EventLogout
EventPurchase
EventRefund
)
func logEvent(e EventType) {
fmt.Printf("Event code: %d\n", e)
}
You can map constant values to strings for dashboards by implementing String().
Best Practices¶
- Always use a named type with iota-based enums.
- Skip the zero value (
_ = iota) if the zero value should mean "unset" or "unknown". - Group related constants in a single
constblock. - Use
UPPER_CASEonly for exported constants that follow existing conventions (e.g.,MaxRetries). Go generally usesCamelCase. - Never hard-code magic numbers — use named constants.
- Add a comment for each constant group explaining what it represents.
- Do not put secrets in constants.
Edge Cases & Pitfalls¶
Pitfall 1 — Inserting a Constant in the Middle¶
If you insert a new constant in an iota block, all following constants shift by 1. This can silently break switch statements or stored database values.
const (
Small = iota // 0
Medium // 1
// insert Large here later — shifts all following values!
Large // 2 → becomes 2 after insert
XLarge // 3 → becomes 3 after insert
)
Mitigation: Assign explicit values when the order matters and values are stored or communicated externally.
Pitfall 2 — iota Only Works Inside const Blocks¶
Pitfall 3 — Constants Must Be Computable at Compile Time¶
Common Mistakes¶
| Mistake | Explanation |
|---|---|
| Trying to assign to a constant | Pi = 3 — compile error |
Using iota in a var block | iota is only valid inside const blocks |
| Forgetting to skip zero for enums | The zero value of a type is the first constant, which may not represent "nothing" |
| Using untyped int constants with typed parameters | May need explicit type conversion |
| Storing iota values in a database without explicit assignment | Inserting a new constant shifts stored values |
Common Misconceptions¶
"const is the same as a final variable in Java" Not quite. Go constants must be computable at compile time. Java final fields can hold runtime values. Go constants cannot.
"iota is a function" No. iota is a predeclared identifier (like true, false, nil). It is not a function call.
"All constants are int" No. Constants can be integers, floats, complex numbers, runes, and strings. Untyped constants are not limited to int.
"You can use iota anywhere" No. iota is only valid inside a const block.
Tricky Points¶
- Repeated expression: Once you write
iotain a const block expression, every subsequent line (that has no explicit value) repeats that same expression formula but with the incrementediota.
- iota is the line number within the block, not the order of assignment:
const (
x = 5 // iota is 0 here, but x is 5 (not using iota)
y = iota // iota is 1, so y = 1
z // iota is 2, so z = 2
)
Test¶
Question 1: What is the value of C in the following code?
Answer
C = 2Question 2: Will this code compile? Why or why not?
Answer
No. `const` initializer must be a compile-time constant expression. `limit` is a variable, so `limit + 5` is not constant.Question 3: What is the value of y?
Answer
y = 1 (iota counts the position in the const block, starting from 0. x is position 0, y is position 1, z is position 2).Tricky Questions¶
Q: Can you have a constant of type []int (slice)? No. Slices are reference types and require runtime memory allocation. Constants must be simple scalar values (numbers, strings, booleans, runes).
Q: What happens if you declare two separate const blocks — does iota continue? No. iota resets to 0 in each new const block.
Q: Can a constant have the value of another constant?
Yes, as long as A is already a constant, B = A * 2 is a constant expression.
Cheat Sheet¶
// Untyped constant
const Pi = 3.14159
// Typed constant
const Pi float64 = 3.14159
// String constant
const Greeting = "Hello"
// Group constant
const (
A = 1
B = 2
C = 3
)
// iota — sequential integers
const (
Zero = iota // 0
One // 1
Two // 2
)
// iota with named type
type Day int
const (
Sunday Day = iota // 0
Monday // 1
// ...
)
// Skip zero
const (
_ = iota // 0 — unused
One // 1
Two // 2
)
// Bit flags
const (
Read = 1 << iota // 1
Write // 2
Execute // 4
)
// File sizes
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)
Self-Assessment Checklist¶
- I can declare a constant using
const - I know the difference between typed and untyped constants
- I can write a
constblock - I understand what
iotadoes and where it starts - I know that
iotaresets to 0 in each newconstblock - I can create a simple enum using
iotaand a named type - I know why skipping zero (using
_) is useful - I understand that constants are resolved at compile time
- I know that constants cannot hold slices, maps, or arrays
- I can replace magic numbers with named constants
Summary¶
constdeclares values that are fixed at compile time and can never be changed.- Use
constfor values that never change: math constants, limits, status codes. iotais a predeclared identifier inconstblocks that starts at 0 and increases by 1 per line.iotaresets to 0 in every newconstblock.- Always pair
iotawith a named type to create a type-safe enum. - Skip the zero value with
_when the zero value should mean "unset" or "unknown". - Constants have no runtime overhead — they are folded by the compiler.
What You Can Build¶
With const and iota you can build:
- Type-safe enumerations (days, months, directions, states)
- HTTP status code libraries
- Permission and role systems
- Color palettes
- Configuration defaults
- Byte size helpers (KB, MB, GB)
- Game tile types, item categories, event types
Further Reading¶
- Official Go specification: Constants
- Go Tour: Constants
- Effective Go: Constants
- Blog: Go constants and enums patterns
Related Topics¶
varand variable declarations- Types and type conversions
- Stringer interface (
fmt.Stringer) - Bit manipulation in Go
- Go's type system and named types