Question
I want to represent a simplified chromosome in Go. A chromosome consists of N bases, and each base can only be one of these values: A, C, T, or G.
I would like to enforce these allowed values using an enum-like type. Since Go does not have enums in the same way as some other languages, what is the most idiomatic way to model this in Go?
Short Answer
By the end of this page, you will understand how Go typically represents enum-like values using custom types and constants, usually with iota when sequential values are useful. You will also see how to model a fixed set of values such as DNA bases (A, C, T, G), how to print them nicely, how to validate them, and how this pattern is used in real Go codebases.
Concept
In Go, the idiomatic replacement for enums is usually:
- Define a custom named type
- Declare a fixed set of typed constants for that type
For example:
type Base int
const (
A Base = iota
C
T
G
)
This gives you a type named Base with a limited set of intended constant values.
Why Go uses this approach
Go keeps the language small and simple. Instead of adding a separate enum feature, it relies on:
- named types
- constants
- methods
- optional validation
This works well because you can still:
- restrict your API to a specific type
- attach methods like
String() - validate input when needed
- use
switchstatements clearly
Important limitation
Go's enum-like pattern does not prevent someone from creating an invalid value directly:
var b Base = 99
That is still valid Go syntax because Base is just a named type underneath. So the idiomatic approach is:
Mental Model
Think of a Go enum as a custom label system rather than a locked box.
- The type is the category, like
Base - The constants are the approved labels, like
A,C,T, andG - Your code agrees to use only those labels
It is similar to creating a set of official badges for a building:
A,C,T,Gare the official badges- the badge type is
Base - someone could theoretically forge a fake badge, so you may still need validation at the entrance
That is how Go enums work: they communicate intent clearly, but validation is still your responsibility when values come from outside your program.
Syntax and Examples
Basic enum-like pattern in Go
package main
import "fmt"
type Base int
const (
A Base = iota
C
T
G
)
func main() {
var b Base = A
fmt.Println(b)
}
This compiles, but printing b will show its numeric value unless you define a String() method.
Adding a String method
package main
import "fmt"
type Base int
const (
A Base = iota
C
T
G
)
func (b Base) String() string {
switch b {
case A:
return "A"
case C:
return "C"
case T:
return "T"
case G:
:
}
}
{
chromosome := []Base{A, C, T, G, A}
_, base := chromosome {
fmt.Print(base, )
}
}
Step by Step Execution
Consider this example:
package main
import "fmt"
type Base byte
const (
A Base = 'A'
C Base = 'C'
T Base = 'T'
G Base = 'G'
)
func main() {
chromosome := []Base{A, G, C}
for _, base := range chromosome {
fmt.Printf("%c\n", base)
}
}
Here is what happens step by step:
-
type Base byte- Creates a new named type called
Base - Its underlying type is
byte
- Creates a new named type called
-
The constants are declared:
Ahas value'A'Chas value'C'Thas value'T'
Real World Use Cases
Enum-like types in Go are commonly used anywhere a value must come from a small fixed set.
Common examples
- Status values:
Pending,Running,Failed,Completed - HTTP or job states:
Queued,Processing,Done - User roles:
Admin,Editor,Viewer - Configuration modes:
Debug,Test,Production - Domain-specific symbols: DNA bases like
A,C,T,G
In your chromosome case
You might use a Base type for:
- storing a chromosome as
Real Codebase Usage
In real Go projects, enum-like values are usually wrapped with a few supporting patterns.
1. Typed constants for domain meaning
type Status int
const (
Pending Status = iota
Running
Failed
Done
)
This makes function signatures clearer:
func SetStatus(s Status) {}
2. String methods for logging and debugging
Developers often implement String() so logs and printed output are readable.
func (s Status) String() string {
switch s {
case Pending:
return "pending"
case Running:
return "running"
case Failed:
return "failed"
case Done:
return "done"
default:
return "unknown"
}
}
Common Mistakes
1. Using plain strings everywhere
This works, but it loses type safety.
func SaveBase(b string) {}
Now any string is accepted:
SaveBase("banana")
Better:
type Base byte
2. Assuming Go enums are fully restricted
Beginners often expect this to be impossible:
var b Base = 99
But it is allowed. Go enum-like types are not closed sets enforced by the compiler.
How to avoid problems
- validate external input
- expose constructor or parser functions
- use methods like
Valid()
Example:
func (b Base) Valid() bool {
switch b {
case A, C, T, G:
:
}
}
Comparisons
| Approach | Example | Pros | Cons | Good for |
|---|---|---|---|---|
Typed int constants | type Base int + iota | Common Go pattern, easy to use in switch | Printed values are numeric unless String() is added | General enum-like values |
Typed byte constants | type Base byte + 'A' | Natural for character data, compact, easy to print | Still needs validation for invalid values | DNA bases, tokens, ASCII symbols |
Typed string constants | + |
Cheat Sheet
Enum-like pattern in Go
type MyType int
const (
ValueA MyType = iota
ValueB
ValueC
)
For DNA bases
type Base byte
const (
A Base = 'A'
C Base = 'C'
T Base = 'T'
G Base = 'G'
)
Validation method
func (b Base) Valid() bool {
switch b {
case A, C, T, G:
return true
default:
return false
}
}
String method
func (b Base) String() string {
return string(b)
}
Parse from string
(Base, ) {
(s) != {
, fmt.Errorf(, s)
}
b := Base(s[])
!b.Valid() {
, fmt.Errorf(, s)
}
b,
}
FAQ
Is there a real enum type in Go?
No. Go usually uses custom named types with constants to represent enum-like values.
What is the most idiomatic way to emulate an enum in Go?
Define a named type and a fixed set of typed constants. Optionally add String() and validation methods.
Should I use iota for all enums in Go?
Not always. iota is great for sequential values, but explicit values are better when they reflect the real domain, such as 'A', 'C', 'T', and 'G'.
Is type Base byte better than type Base int for DNA bases?
Often yes, because DNA bases are naturally single characters and can be stored and printed conveniently.
Can invalid enum values still exist in Go?
Yes. A variable of a named type can still hold other underlying values unless you validate them.
How do I print enum values nicely in Go?
Implement a String() method, or use a character-based underlying type like byte when appropriate.
How should I validate enum values from user input?
Mini Project
Description
Build a small Go program that models a DNA sequence using an enum-like Base type. The project demonstrates how to define a fixed set of allowed values, validate input, and work with a slice of strongly typed bases instead of raw strings.
Goal
Create a program that parses a DNA string, stores it as []Base, validates every character, and counts how many times each base appears.
Requirements
- Define a
Basetype for DNA values. - Create constants for
A,C,T, andG. - Write a function to parse a string into a slice of
Basevalues. - Reject invalid characters with a clear error.
- Count and print the number of each base in the sequence.
Keep learning
Related questions
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.
Convert String to Integer in Go: Idiomatic Parsing with strconv.Atoi
Learn the idiomatic way to convert a string to an int in Go using strconv.Atoi, with examples, errors, and common mistakes.