Enums in Go: Idiomatic Way and Magic Under the Hood

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Doesn't Go Say "enum"?

\n\n

If you came to Go from Python, Java, or C#, you've probably experienced a mild culture shock: Go does not have the enum keyword. No built-in enumerations with automatic numbering, no .name() or .valueOf() methods. Just you, the compiler, and… constants.

\n\n

The question "What is an idiomatic way of representing enums in Go?" is one of the most popular on Stack Overflow in the Golang section (thousands of views). And this is no accident. Every beginner eventually hits a wall: "How do I make an enum the Go way?"

\n\n

In this article, we won't just give the answer. We'll look under the hood: how Go handles constants, what iota is, and why the compiler doesn't consider two different int values as different types. And at the end, you'll learn how to make an enum safe, convenient, and elegant.

\n\n

The Problem: Why Are Simple Constants Bad?

\n\n

The first thing that comes to mind is to declare several constants:

\n\n
const (\n    StatusActive   = 1\n    StatusInactive = 2\n    StatusPending  = 3\n)
\n\n

It seems to work. But there's a nuance: any function that takes an int will swallow the value 999 or -42. The compiler won't say a word. This isn't an enumeration — it's just numbers with names.

\n\n

A real enum should:

\n
    \n
  • Restrict the set of allowed values;
  • \n
  • Be type-safe (you can't pass an int where a status is expected);
  • \n
  • Be readable and easily extensible.
  • \n
\n\n

Go solves this problem idiomatically: through custom types and iota.

\n\n

The Idiomatic Way: Type + iota

\n\n

Here's what the canonical answer from that very Stack Overflow looks like:

\n\n
type Status int\n\nconst (\n    StatusActive   Status = iota // 0\n    StatusInactive               // 1\n    StatusPending                // 2\n    StatusDeleted                // 3\n)
\n\n

What happened here?

\n
    \n
  • type Status int — we created a new named type. Yes, under the hood it's an int, but for the compiler, Status and int are different things.
  • \n
  • iota is a sequential integer generator that only works inside a const block. Each new line increments the value by 1.
  • \n
\n\n

Now a function that takes a Status will not compile if you try to pass it a plain int:

\n\n
func Process(s Status) {\n    // ...\n}\n\nfunc main() {\n    Process(StatusActive) // OK\n    Process(1)            // Compilation error! Cannot use 1 (type int) as type Status\n}
\n\n

This is type safety — the first and main reason to use custom types for enums.

\n\n

Why iota Instead of Manual Numbering?

\n\n

Imagine you added a new status in the middle of the list. With manual numbering, you would have to rewrite all subsequent values. iota does this automatically. It's not just convenient — it's idiomatic.

\n\n

Under the Hood: How Go Sees an Enum

\n\n

Let's break down what actually happens at compile time.

\n\n

1. iota is Not Runtime

\n\n

iota does not exist during program execution. It is a preprocessor tool (more precisely, part of the package compiler). At compile time, all iota are replaced with specific numbers. In the resulting binary, you will simply see 0, 1, 2.

\n\n

You can verify this with go tool compile or just by looking at the assembly code:

\n\n
// Source code\nconst (\n    A                

Blogs

Book Recommendations