Skip to main content
Enumerated types (enums) are types with a fixed number of possible values, each with a distinct name. Go doesn’t have an enum type as a distinct language feature, but enums are simple to implement using existing language idioms.

Basic enum pattern

package main

import "fmt"

// Our enum type ServerState has an underlying int type.
type ServerState int

// The possible values for ServerState are defined as constants.
// The special keyword iota generates successive constant values
// automatically; in this case 0, 1, 2 and so on.
const (
    StateIdle ServerState = iota
    StateConnected
    StateError
    StateRetrying
)

func main() {
    ns := transition(StateIdle)
    fmt.Println(ns)

    ns2 := transition(ns)
    fmt.Println(ns2)
}

// transition emulates a state transition for a server;
// it takes the existing state and returns a new state.
func transition(s ServerState) ServerState {
    switch s {
    case StateIdle:
        return StateConnected
    case StateConnected, StateRetrying:
        return StateIdle
    case StateError:
        return StateError
    default:
        panic(fmt.Errorf("unknown state: %s", s))
    }
}

Making enums printable

Implement the fmt.Stringer interface to make your enum values readable:
// By implementing the fmt.Stringer interface, values of ServerState
// can be printed out or converted to strings.
var stateName = map[ServerState]string{
    StateIdle:      "idle",
    StateConnected: "connected",
    StateError:     "error",
    StateRetrying:  "retrying",
}

func (ss ServerState) String() string {
    return stateName[ss]
}
Output:
connected
idle
For types with many possible values, the stringer tool can be used with go:generate to automate creating the String() method. See this post for details.

Understanding iota

iota is a special constant generator that creates successive integer constants:
const (
    A = iota  // 0
    B         // 1
    C         // 2
)

Type safety

Using a custom type provides compile-time type safety:
func transition(s ServerState) ServerState {
    // ...
}

// ✓ This works
transition(StateIdle)

// ✗ This won't compile - type mismatch
// transition(1)

// If we had used plain int:
// func transition(s int) int { ... }
// Both would work, losing type safety
By defining a custom type like ServerState, you get compile-time type checking. You can’t accidentally pass a plain int to functions expecting a ServerState.

Common patterns

Status codes

type Status int

const (
    StatusPending Status = iota
    StatusApproved
    StatusRejected
)

Weekdays

type Weekday int

const (
    Sunday Weekday = iota
    Monday
    Tuesday
    // ...
)

Permissions (flags)

type Permission int

const (
    Read Permission = 1 << iota
    Write
    Execute
)

Log levels

type LogLevel int

const (
    Debug LogLevel = iota
    Info
    Warning
    Error
)

Advanced: Validation method

func (ss ServerState) Valid() bool {
    return ss >= StateIdle && ss <= StateRetrying
}

// Usage
if !state.Valid() {
    return fmt.Errorf("invalid state: %d", state)
}

Best practices

Always create a custom type for your enum, don’t use plain int. This provides type safety.
Implement the fmt.Stringer interface to make your enums readable in logs and debugging.
Add comments explaining what each enum value means, especially if the names aren’t self-explanatory.
Use iota for automatic numbering. Start from 0 unless you have a specific reason to start elsewhere.
For enums that might come from external sources, provide a validation method.

Constants

Learn about constants in Go

Switch statements

Use enums with switch

Build docs developers (and LLMs) love